def test_reliabilities(self): # test scalar vs vector version frequency = 573.3 height1 = 194.0 height2 = 9.1 refractivity = 314.0 # Typical dielectric = 15 # Typical for ground conductivity = 0.005 # Typical for ground climate = 5 # Continental temperate polarization = 0 # Vertical confidence = 0.5 mdvar = 12 refractivity_final = False reliabilities = np.arange(0.1, 1.0, 0.1) losses, _, _, _, _ = itm.point_to_point(PROFILE, height1, height2, dielectric, .005, refractivity, frequency, climate, polarization, confidence, reliabilities) self.assertEqual(len(losses), len(reliabilities)) for rel, exp_loss in zip(reliabilities, losses): loss, _, _, _, _ = itm.point_to_point(PROFILE, height1, height2, dielectric, .005, refractivity, frequency, climate, polarization, confidence, rel) self.assertEqual(loss, exp_loss)
def test_default_arg(self): frequency = 573.3 height1 = 194.0 height2 = 9.1 refractivity = 314.0 # Typical dielectric = 15 # Typical for ground conductivity = 0.005 # Typical for ground climate = 5 # Continental temperate polarization = 0 # Vertical confidence = 0.5 reliability = 0.5 # Expected default arguments mdvar = 12 refractivity_final = False loss1, _, _, _, _ = itm.point_to_point(PROFILE, height1, height2, dielectric, .005, refractivity, frequency, climate, polarization, confidence, reliability, mdvar, refractivity_final) loss2, _, _, _, _ = itm.point_to_point(PROFILE, height1, height2, dielectric, .005, refractivity, frequency, climate, polarization, confidence, reliability) self.assertEqual(loss1, loss2)
def test_qkpfl_path1979_its(self): confidence_vals = [0.5, 0.9, 0.1] reliability_vals = [0.01, 0.1, 0.5, 0.9, 0.99] expected_losses = [ 144.3, 154.1, 134.4, 150.9, 159.5, 142.3, 157.6, 165.7, 149.4, 161.6, 169.9, 153.3, 164.9, 173.6, 156.2 ] frequency = 573.3 height1 = 194.0 height2 = 9.1 refractivity = 314.0 # Typical refractivity_final = True dielectric = 15 # Typical for ground conductivity = 0.005 # Typical for ground climate = 5 # Continental temperate polarization = 0 # Vertical mdvar = 12 k = 0 for reliability in reliability_vals: for confidence in confidence_vals: loss, ver0, ver1, err, mode = itm.point_to_point( PROFILE, height1, height2, dielectric, .005, refractivity, frequency, climate, polarization, confidence, reliability, mdvar, refractivity_final) self.assertAlmostEqual(loss, expected_losses[k], 1) k += 1
def test_qkpfl_path2200_its(self): confidence_vals = [0.5, 0.9, 0.1] reliability_vals = [0.01, 0.1, 0.5, 0.9, 0.99] expected_losses = [ 128.6, 137.6, 119.6, 132.2, 140.8, 123.5, 135.8, 144.3, 127.2, 138.0, 146.5, 129.4, 139.7, 148.4, 131.0 ] frequency = 41.5 height1 = 143.9 height2 = 8.5 refractivity = 314.0 # Typical refractivity_final = True dielectric = 15 # Typical for ground conductivity = 0.005 # Typical for ground climate = 5 # Continental temperate polarization = 0 # Vertical mdvar = 12 k = 0 for reliability in reliability_vals: for confidence in confidence_vals: loss, ver0, ver1, err, mode = itm.point_to_point( PROFILE, height1, height2, dielectric, .005, refractivity, frequency, climate, polarization, confidence, reliability, mdvar, refractivity_final) self.assertAlmostEqual(loss, expected_losses[k], 1) k += 1
def test_horizon_angles_los(self): refractivity = 314. PROFILE = [5, 28.5, 10, 10, 8, 9, 11, 12] a0, a1, _, _ = _GetHorizonAnglesLegacy(PROFILE, 100, 50, refractivity) _, v0, v1, _, _ = itm.point_to_point(PROFILE, 100, 50, dielectric=15, conductivity=.005, refractivity=refractivity, freq_mhz=41.5, climate=5, polarization=0, confidence=0.5, reliabilities=0.5) self.assertEqual(a0, v0) self.assertEqual(a1, v1)
def test_horizon_angles(self): refractivity = 314. a0, a1, d0, d1 = _GetHorizonAnglesLegacy(PROFILE, 143.9, 8.5, refractivity) _, v0, v1, _, _ = itm.point_to_point(PROFILE, 143.9, 8.5, dielectric=15, conductivity=.005, refractivity=refractivity, freq_mhz=41.5, climate=5, polarization=0, confidence=0.5, reliabilities=0.5) self.assertAlmostEqual(a0, np.arctan(-0.003900) * 180. / np.pi, 4) self.assertAlmostEqual(a1, np.arctan(0.000444) * 180. / np.pi, 4) self.assertAlmostEqual(d0, 55357.7, 1) self.assertAlmostEqual(d1, 19450.0, 1) # test exactness of new method vs old method self.assertEqual(a0, v0) self.assertEqual(a1, v1)
def CalcItmPropagationLoss(lat_cbsd, lon_cbsd, height_cbsd, lat_rx, lon_rx, height_rx, cbsd_indoor=False, reliability=0.5, freq_mhz=3625., its_elev=None, is_height_cbsd_amsl=False, return_internals=False): """Implements the WinnForum-compliant ITM point-to-point propagation model. According to WinnForum spec R2-SGN-17, R2-SGN-22 and R2-SGN-5 to 10. One can use this routine in 3 ways: reliability = -1 : to get the average path loss reliability in [0,1] : to get a pathloss for given quantile sequence of reliabilities: to get an array of pathloss. Used to obtain inverse CDF of the pathloss. Inputs: lat_cbsd, lon_cbsd, height_cbsd: Lat/lon (deg) and height AGL (m) of CBSD lat_rx, lon_rx, height_rx: Lat/lon (deg) and height AGL (m) of Rx point cbsd_indoor: CBSD indoor status - Default=False. reliability: Reliability. Default is 0.5 (median value) Different options: value in [0,1]: returns the CDF quantile -1: returns the mean path loss iterable sequence: returns a list of path losses freq_mhz: Frequency (MHz). Default is mid-point of band. its_elev: Optional profile to use (in ITM format). Default=None If not specified, it is extracted from the terrain. is_height_cbsd_amsl: If True, the CBSD height shall be considered as AMSL (Average mean sea level). return_internals: If True, returns internal variables. Returns: A namedtuple of: db_loss Path Loss in dB, either a scalar if reliability is scalar or a list of path losses if reliability is an iterable. incidence_angles: A namedtuple of hor_cbsd: Horizontal departure angle (bearing) from CBSD to Rx ver_cbsd: Vertical departure angle at CBSD hor_rx: Horizontal incidence angle (bearing) from Rx to CBSD ver_rx: Vertical incidence angle at Rx internals: A dictionary of internal data for advanced analysis (only if return_internals=True): itm_err_num: ITM error code from ItmErrorCode (see GetInfoOnItmCode). itm_str_mode: String containing description of dominant prop mode. dist_km: Distance between end points (km). prof_d_km ndarray of distances (km) - x values to plot terrain. prof_elev ndarray of terrain heightsheights (m) - y values to plot terrain, Raises: Exception if input parameters invalid or out of range. """ # Case of same points if (lat_cbsd == lat_rx and lon_cbsd == lon_rx): return _PropagResult(db_loss=0 if np.isscalar(reliability) else [0] * len(reliability), incidence_angles=_IncidenceAngles(0, 0, 0, 0), internals=None) # Sanity checks on input parameters if freq_mhz < 40.0 or freq_mhz > 10000: raise Exception('Frequency outside range [40MHz - 10GHz]') if is_height_cbsd_amsl: altitude_cbsd = drive.terrain_driver.GetTerrainElevation( lat_cbsd, lon_cbsd) height_cbsd = height_cbsd - altitude_cbsd # Ensure minimum height of 1 meter if height_cbsd < 1: height_cbsd = 1 if height_rx < 1: height_rx = 1 # Internal ITM parameters are always set to following values in WF version: confidence = 0.5 # Confidence (always 0.5) dielec = 25. # Dielectric constant (always 25.) conductivity = 0.02 # Conductivity (always 0.02) polarization = 1 # Polarization (always vertical = 1) mdvar = 13 # Get the terrain profile, using Vincenty great circle route, and WF # standard (bilinear interp; 1500 pts for all distances over 45 km) if its_elev is None: its_elev = drive.terrain_driver.TerrainProfile(lat1=lat_cbsd, lon1=lon_cbsd, lat2=lat_rx, lon2=lon_rx, target_res_meter=30., do_interp=True, max_points=1501) # Find the midpoint of the great circle path dist_km, bearing_cbsd, bearing_rx = vincenty.GeodesicDistanceBearing( lat_cbsd, lon_cbsd, lat_rx, lon_rx) latmid, lonmid, _ = vincenty.GeodesicPoint(lat_cbsd, lon_cbsd, dist_km / 2., bearing_cbsd) # Determine climate value, based on ITU-R P.617 method: climate = drive.climate_driver.TropoClim(latmid, lonmid) # If the common volume lies over the sea, the climate value to use depends # on the climate values at either end. A simple min() function should # properly implement the logic, since water is the max. if climate == 7: climate = min(drive.climate_driver.TropoClim(lat_cbsd, lon_cbsd), drive.climate_driver.TropoClim(lat_rx, lon_rx)) # Look up the refractivity at the path midpoint, if not explicitly provided refractivity = drive.refract_driver.Refractivity(latmid, lonmid) # Call ITM prop loss. reliabilities = reliability do_avg = False if np.isscalar(reliabilities) and reliability == -1: # Pathloss mean: average the value for 1% to 99% included reliabilities = np.arange(0.01, 1.0, 0.01) do_avg = True db_loss, ver_cbsd, ver_rx, str_mode, err_num = itm.point_to_point( its_elev, height_cbsd, height_rx, dielec, conductivity, refractivity, freq_mhz, climate, polarization, confidence, reliabilities, mdvar, False) if do_avg: db_loss = -10 * np.log10(np.mean(10**(-np.array(db_loss) / 10.))) # Add indoor losses if cbsd_indoor: if np.isscalar(db_loss): db_loss += 15 else: db_loss = [loss + 15 for loss in db_loss] # Create distance/terrain arrays for plotting if desired internals = None if return_internals: prof_d_km = (its_elev[1] / 1000.) * np.arange(len(its_elev) - 2) prof_elev = np.asarray(its_elev[2:]) internals = { 'itm_err_num': err_num, 'itm_str_mode': str_mode, 'dist_km': dist_km, 'prof_d_km': prof_d_km, 'prof_elev': prof_elev } return _PropagResult(db_loss=db_loss, incidence_angles=_IncidenceAngles( hor_cbsd=bearing_cbsd, ver_cbsd=ver_cbsd, hor_rx=bearing_rx, ver_rx=ver_rx), internals=internals)