@mytec: iter2.4 ready for testing
This commit is contained in:
@@ -55,6 +55,7 @@ from app.services.indoor_service import indoor_service
|
||||
from app.services.atmospheric_service import atmospheric_service
|
||||
from app.services.parallel_coverage_service import (
|
||||
calculate_coverage_parallel, get_cpu_count, get_parallel_backend,
|
||||
CancellationToken,
|
||||
)
|
||||
|
||||
|
||||
@@ -280,7 +281,8 @@ class CoverageService:
|
||||
async def calculate_coverage(
|
||||
self,
|
||||
site: SiteParams,
|
||||
settings: CoverageSettings
|
||||
settings: CoverageSettings,
|
||||
cancel_token: Optional[CancellationToken] = None,
|
||||
) -> List[CoveragePoint]:
|
||||
"""
|
||||
Calculate coverage grid for a single site
|
||||
@@ -352,6 +354,32 @@ class CoverageService:
|
||||
f"pre-computed {len(grid)} elevations")
|
||||
_clog(f"━━━ PHASE 2 done: {terrain_time:.1f}s ━━━")
|
||||
|
||||
# ━━━ PHASE 2.5: Vectorized pre-computation (GPU/NumPy) ━━━
|
||||
from app.services.gpu_service import gpu_service
|
||||
|
||||
t_gpu = time.time()
|
||||
grid_lats = np.array([lat for lat, lon in grid])
|
||||
grid_lons = np.array([lon for lat, lon in grid])
|
||||
|
||||
pre_distances = gpu_service.precompute_distances(
|
||||
grid_lats, grid_lons, site.lat, site.lon
|
||||
)
|
||||
pre_path_loss = gpu_service.precompute_path_loss(
|
||||
pre_distances, site.frequency, site.height
|
||||
)
|
||||
|
||||
# Build lookup dict for point loop
|
||||
precomputed = {}
|
||||
for i, (lat, lon) in enumerate(grid):
|
||||
precomputed[(lat, lon)] = {
|
||||
'distance': float(pre_distances[i]),
|
||||
'path_loss': float(pre_path_loss[i]),
|
||||
}
|
||||
|
||||
gpu_time = time.time() - t_gpu
|
||||
_clog(f"━━━ PHASE 2.5: Vectorized pre-computation done: {gpu_time:.3f}s "
|
||||
f"({len(grid)} points, backend={'GPU' if gpu_service.available else 'CPU/NumPy'}) ━━━")
|
||||
|
||||
# ━━━ PHASE 3: Point calculation ━━━
|
||||
dominant_path_service._log_count = 0 # Reset diagnostic counter
|
||||
t_points = time.time()
|
||||
@@ -368,12 +396,15 @@ class CoverageService:
|
||||
loop = asyncio.get_event_loop()
|
||||
result_dicts, timing = await loop.run_in_executor(
|
||||
None,
|
||||
calculate_coverage_parallel,
|
||||
grid, point_elevations,
|
||||
site.model_dump(), settings.model_dump(),
|
||||
self.terrain._tile_cache,
|
||||
buildings, streets, water_bodies, vegetation_areas,
|
||||
site_elevation, num_workers, _clog,
|
||||
lambda: calculate_coverage_parallel(
|
||||
grid, point_elevations,
|
||||
site.model_dump(), settings.model_dump(),
|
||||
self.terrain._tile_cache,
|
||||
buildings, streets, water_bodies, vegetation_areas,
|
||||
site_elevation, num_workers, _clog,
|
||||
cancel_token=cancel_token,
|
||||
precomputed=precomputed,
|
||||
),
|
||||
)
|
||||
|
||||
# Convert dicts back to CoveragePoint objects
|
||||
@@ -389,10 +420,13 @@ class CoverageService:
|
||||
loop = asyncio.get_event_loop()
|
||||
points, timing = await loop.run_in_executor(
|
||||
None,
|
||||
self._run_point_loop,
|
||||
grid, site, settings, buildings, streets,
|
||||
spatial_idx, water_bodies, vegetation_areas,
|
||||
site_elevation, point_elevations
|
||||
lambda: self._run_point_loop(
|
||||
grid, site, settings, buildings, streets,
|
||||
spatial_idx, water_bodies, vegetation_areas,
|
||||
site_elevation, point_elevations,
|
||||
cancel_token=cancel_token,
|
||||
precomputed=precomputed,
|
||||
),
|
||||
)
|
||||
|
||||
points_time = time.time() - t_points
|
||||
@@ -423,7 +457,8 @@ class CoverageService:
|
||||
async def calculate_multi_site_coverage(
|
||||
self,
|
||||
sites: List[SiteParams],
|
||||
settings: CoverageSettings
|
||||
settings: CoverageSettings,
|
||||
cancel_token: Optional[CancellationToken] = None,
|
||||
) -> List[CoveragePoint]:
|
||||
"""
|
||||
Calculate combined coverage from multiple sites
|
||||
@@ -437,7 +472,7 @@ class CoverageService:
|
||||
|
||||
# Get all individual coverages
|
||||
all_coverages = await asyncio.gather(*[
|
||||
self.calculate_coverage(site, settings)
|
||||
self.calculate_coverage(site, settings, cancel_token)
|
||||
for site in sites
|
||||
])
|
||||
|
||||
@@ -485,7 +520,8 @@ class CoverageService:
|
||||
def _run_point_loop(
|
||||
self, grid, site, settings, buildings, streets,
|
||||
spatial_idx, water_bodies, vegetation_areas,
|
||||
site_elevation, point_elevations
|
||||
site_elevation, point_elevations,
|
||||
cancel_token=None, precomputed=None,
|
||||
):
|
||||
"""Sync point loop - runs in ThreadPoolExecutor, bypasses event loop."""
|
||||
points = []
|
||||
@@ -496,14 +532,22 @@ class CoverageService:
|
||||
log_interval = max(1, total // 20)
|
||||
|
||||
for i, (lat, lon) in enumerate(grid):
|
||||
if cancel_token and cancel_token.is_cancelled:
|
||||
_clog(f"Cancelled at {i}/{total}")
|
||||
break
|
||||
|
||||
if i % log_interval == 0:
|
||||
_clog(f"Progress: {i}/{total} ({i*100//total}%)")
|
||||
|
||||
pre = precomputed.get((lat, lon)) if precomputed else None
|
||||
|
||||
point = self._calculate_point_sync(
|
||||
site, lat, lon, settings, buildings, streets,
|
||||
spatial_idx, water_bodies, vegetation_areas,
|
||||
site_elevation, point_elevations.get((lat, lon), 0.0),
|
||||
timing
|
||||
timing,
|
||||
precomputed_distance=pre.get('distance') if pre else None,
|
||||
precomputed_path_loss=pre.get('path_loss') if pre else None,
|
||||
)
|
||||
if point.rsrp >= settings.min_signal:
|
||||
points.append(point)
|
||||
@@ -523,17 +567,25 @@ class CoverageService:
|
||||
vegetation_areas: List[VegetationArea],
|
||||
site_elevation: float,
|
||||
point_elevation: float,
|
||||
timing: dict
|
||||
timing: dict,
|
||||
precomputed_distance: Optional[float] = None,
|
||||
precomputed_path_loss: Optional[float] = None,
|
||||
) -> CoveragePoint:
|
||||
"""Fully synchronous point calculation. All terrain tiles must be pre-loaded."""
|
||||
|
||||
# Distance
|
||||
distance = TerrainService.haversine_distance(site.lat, site.lon, lat, lon)
|
||||
# Distance (use precomputed if available)
|
||||
if precomputed_distance is not None:
|
||||
distance = precomputed_distance
|
||||
else:
|
||||
distance = TerrainService.haversine_distance(site.lat, site.lon, lat, lon)
|
||||
if distance < 1:
|
||||
distance = 1
|
||||
|
||||
# Base path loss
|
||||
path_loss = self._okumura_hata(distance, site.frequency, site.height, 1.5)
|
||||
# Base path loss (use precomputed if available)
|
||||
if precomputed_path_loss is not None:
|
||||
path_loss = precomputed_path_loss
|
||||
else:
|
||||
path_loss = self._okumura_hata(distance, site.frequency, site.height, 1.5)
|
||||
|
||||
# Antenna pattern
|
||||
antenna_loss = 0.0
|
||||
|
||||
Reference in New Issue
Block a user