def test_small_height(self): lat1, lng1 = 37.756672, -122.508512 lat2, lng2 = 37.754406, -122.388342 res_bad = wf_itm.CalcItmPropagationLoss(lat1, lng1, 0, lat2, lng2, -1, cbsd_indoor=False, reliability=0.5, freq_mhz=3625.) res_expected = wf_itm.CalcItmPropagationLoss(lat1, lng1, 1, lat2, lng2, 1, cbsd_indoor=False, reliability=0.5, freq_mhz=3625.) self.assertEqual(res_bad.db_loss, res_expected.db_loss) self.assertEqual(res_bad.incidence_angles, res_expected.incidence_angles)
def test_op_mode(self): lat1, lng1, height1 = 37.756672, -122.508512, 20.0 lat2, lng2, height2 = 37.754406, -122.388342, 10.0 reliabilities = [0.1, 0.5, 0.9] expected_losses = [213.68, 214.26, 214.62] for rel, exp_loss in zip(reliabilities, expected_losses): res = wf_itm.CalcItmPropagationLoss(lat1, lng1, height1, lat2, lng2, height2, reliability=rel, freq_mhz=3625., return_internals=True) self.assertAlmostEqual(res.db_loss, exp_loss, 2) self.assertEqual(res.internals['itm_err_num'], wf_itm.ItmErrorCode.WARNING) # Double horizon # Reliability list input res = wf_itm.CalcItmPropagationLoss(lat1, lng1, height1, lat2, lng2, height2, reliability=reliabilities, freq_mhz=3625.) for loss, exp_loss in zip(res.db_loss, expected_losses): self.assertAlmostEqual(loss, exp_loss, 2)
def test_indoor(self): lat1, lng1, height1 = 37.756672, -122.508512, 20.0 lat2, lng2, height2 = 37.754406, -122.388342, 10.0 # scalar version res_outdoor = wf_itm.CalcItmPropagationLoss(lat1, lng1, height1, lat2, lng2, height2, cbsd_indoor=False, reliability=0.5, freq_mhz=3625.) res_indoor = wf_itm.CalcItmPropagationLoss(lat1, lng1, height1, lat2, lng2, height2, cbsd_indoor=True, reliability=0.5, freq_mhz=3625.) self.assertEqual(res_indoor.db_loss, res_outdoor.db_loss + 15) # vector version reliabilities = np.arange(0.01, 1.0, 0.01) res_outdoor = wf_itm.CalcItmPropagationLoss(lat1, lng1, height1, lat2, lng2, height2, cbsd_indoor=False, reliability=reliabilities, freq_mhz=3625.) res_indoor = wf_itm.CalcItmPropagationLoss(lat1, lng1, height1, lat2, lng2, height2, cbsd_indoor=True, reliability=reliabilities, freq_mhz=3625.) self.assertEqual( np.max( np.abs( np.array(res_indoor.db_loss) - np.array(res_outdoor.db_loss) - 15)), 0)
def test_angles_vs_legacy(self): # More extensive testing of the vertical incidence angle # obtained from C module vs legacy python-only method used prior to April 2017 np.random.seed(12345) n_links = 500 pairs = testutils.MakeLatLngPairs(n_links, 10, 50000, lat_min=37, lat_max=38, lng_min=-123, lng_max=-122) height_tx = 20 height_rx = 10 for pair in pairs: profile = drive.terrain_driver.TerrainProfile(*pair, target_res_meter=30., do_interp=True, max_points=1501) # Compute angles the legacy way a_tx, a_rx, _, _ = _GetHorizonAnglesLegacy(profile, height_tx, height_rx, 314) # Get angle from he ITM model _, inc_angles, _ = wf_itm.CalcItmPropagationLoss(pair[0], pair[1], height_tx, pair[2], pair[3], height_rx, its_elev=profile) self.assertAlmostEqual(inc_angles.ver_cbsd, a_tx, 14) self.assertAlmostEqual(inc_angles.ver_rx, a_rx, 14)
def test_average_itm(self): lat1, lng1, height1 = 37.756672, -122.508512, 20.0 lat2, lng2, height2 = 37.754406, -122.388342, 1.5 reliabilities = np.arange(0.01, 1.0, 0.01) losses = [] for rel in reliabilities: res = wf_itm.CalcItmPropagationLoss(lat1, lng1, height1, lat2, lng2, height2, reliability=rel, freq_mhz=3625.) losses.append(res.db_loss) avg_res = wf_hybrid.CalcHybridPropagationLoss(lat1, lng1, height1, lat2, lng2, height2, reliability=-1, freq_mhz=3625., region='SUBURBAN', return_internals=True) self.assertEqual(avg_res.internals['hybrid_opcode'], wf_hybrid.HybridMode.ITM_DOMINANT) self.assertAlmostEqual( avg_res.db_loss, -10 * np.log10(np.mean(10**(-np.array(losses) / 10.))), 5)
def get_im_point(cbsd1, cbsd2): """ Call ITM propagation model to calculate Interference Metric of CBSD1 that is interferred by CBSD2 Using Point Coordination Input: cbsd1: CBSD object that is interferred. :type cbsd1: CBSD cbsd2: CBSD object that is interferring (as noise). :type cbsd2: CBSD Returns: Interference Metrics as float """ # Call ITM Propagation Model to calculate signal loss from cbsd2 at the location of cbsd1 loss = wf_itm.CalcItmPropagationLoss(cbsd2.latitude, cbsd2.longitude, cbsd2.height, cbsd1.latitude, cbsd1.longitude, cbsd1.height, cbsd_indoor=cbsd2.indoor, reliability=0.5) # Calculate Signal Strength by substracting loss from the transmitter power Ix = cbsd2.TxPower - loss[0] # Use Global I_min, I_max to calculate the Interference Metric if Ix < I_min: return 0 elif Ix > I_max: return 1.0 else: return float(Ix - I_min) / (I_max - I_min)
def get_rx_signal_point((cbsd, location)): """ This Method returns the Rx Signal Strength of a CBSD to a location using ITM Model Input: cbsd: a cbsd object :type cbsd CBSD location, the coordinate of the location :type location Tuple[float, float] Returns: Rx_Signal: The signal strength, float """ lat_rx = location[0] # latitude of the location lon_rx = location[1] # longitude of the location height_rx = 0 # Default AGL of the Rx location is set to 0 # call the method in wf_itm (itm model) to calculate the propagation loss from the CBSD to the location loss = wf_itm.CalcItmPropagationLoss(cbsd.latitude, cbsd.longitude, cbsd.height, lat_rx, lon_rx, height_rx, cbsd_indoor=cbsd.indoor, reliability=0.5) # Calculate Signal Strength by substracting loss from the transmitter power Rx_Signal = cbsd.TxPower - loss[0] return Rx_Signal
def computeInterferenceFssBlocking(cbsd_grant, constraint, fss_info, max_eirp): """Computes interference that a grant causes to a FSS protection point. Routine to compute interference neighborhood grant causes to FSS protection point for blocking passband Args: cbsd_grant: A CBSD grant of type |data.CbsdGrantInfo|. constraint: The protection constraint of type |data.ProtectionConstraint|. fss_info: The FSS information of type |data.FssInformation|. max_eirp: The maximum EIRP allocated to the grant during IAP procedure. Returns: The interference contribution(dBm). """ # Get the propagation loss and incident angles for FSS entity # blocking channels db_loss, incidence_angles, _ = wf_itm.CalcItmPropagationLoss( cbsd_grant.latitude, cbsd_grant.longitude, cbsd_grant.height_agl, constraint.latitude, constraint.longitude, fss_info.height_agl, cbsd_grant.indoor_deployment, reliability=-1, freq_mhz=FREQ_PROP_MODEL_MHZ) # Compute CBSD antenna gain in the direction of protection point ant_gain = antenna.GetStandardAntennaGains(incidence_angles.hor_cbsd, cbsd_grant.antenna_azimuth, cbsd_grant.antenna_beamwidth, cbsd_grant.antenna_gain) # Compute FSS antenna gain in the direction of CBSD fss_ant_gain = antenna.GetFssAntennaGains(incidence_angles.hor_rx, incidence_angles.ver_rx, fss_info.pointing_azimuth, fss_info.pointing_elevation, fss_info.max_gain_dbi) # Get the total antenna gain by summing the antenna gains from CBSD to FSS # and FSS to CBSD effective_ant_gain = ant_gain + fss_ant_gain # Compute EIRP of CBSD grant inside the frequency range of # protection constraint eff_bandwidth = ( min(cbsd_grant.high_frequency, constraint.high_frequency) - cbsd_grant.low_frequency) if eff_bandwidth <= 0: raise ValueError( 'Computing FSS blocking on grant fully inside FSS passband') eirp = getEffectiveSystemEirp(max_eirp, cbsd_grant.antenna_gain, effective_ant_gain, eff_bandwidth) # Calculate the interference contribution interference = eirp - getFssMaskLoss(cbsd_grant, constraint) - db_loss return interference
def test_average(self): lat1, lng1, height1 = 37.756672, -122.508512, 20.0 lat2, lng2, height2 = 37.754406, -122.388342, 10.0 reliabilities = np.arange(0.01, 1.0, 0.01) # vector call results = wf_itm.CalcItmPropagationLoss(lat1, lng1, height1, lat2, lng2, height2, reliability=reliabilities, freq_mhz=3625.) # Scalar call for rel, exp_loss in zip(reliabilities, results.db_loss): res = wf_itm.CalcItmPropagationLoss(lat1, lng1, height1, lat2, lng2, height2, reliability=rel, freq_mhz=3625.) self.assertEqual(res.db_loss, exp_loss) # Internal average avg_res = wf_itm.CalcItmPropagationLoss(lat1, lng1, height1, lat2, lng2, height2, reliability=-1, freq_mhz=3625.) self.assertAlmostEqual( avg_res.db_loss, -10 * np.log10(np.mean(10**(-np.array(results.db_loss) / 10.))), 5)
def calculateOobeInterference(grants_cbsds_oobe_info, fss_point, fss_info): """Calculates the OOBE interference value (MCBSDi,ch + GCBSDi + PLinvi + GFSSi). The interference values are calculated based on the grant with the highest highFrequency value, and added back into the grant object under key: 'oobe_interference'. Args: grants_cbsds_oobe_info : List of dictionaries containing the highest grant of their CBSD and a reference to the CBSD. fss_point: The FSS location as a (longitude, latitude) tuple. fss_info: The |data.FssInformation| of the FSS. """ # Get values of MCBSD,GCBSD,GFSS and LCBSD for each CBSD. for grant in grants_cbsds_oobe_info: if not grant['cbsd']['grants']: continue cbsd = data.constructCbsdGrantInfo(grant['cbsd']['registration'], None) # Get the MCBSD (ie the conducted OOBE power) mcbsd = grant['mcbsd'] # Computes the path loss lcbsd, incidence_angle, _ = wf_itm.CalcItmPropagationLoss( cbsd.latitude, cbsd.longitude, cbsd.height_agl, fss_point[1], fss_point[0], fss_info.height_agl, cbsd.indoor_deployment, reliability=-1, freq_mhz=interf.FREQ_PROP_MODEL_MHZ) # The CBSD antenna gain towards FSS gcbsd = antenna.GetStandardAntennaGains( incidence_angle.hor_cbsd, cbsd.antenna_azimuth, cbsd.antenna_beamwidth, cbsd.antenna_gain) # The FSS antenna gain gfss = antenna.GetFssAntennaGains( incidence_angle.hor_rx, incidence_angle.ver_rx, fss_info.pointing_azimuth, fss_info.pointing_elevation, fss_info.max_gain_dbi) # The OOBE interference oobe_interference = mcbsd + gcbsd - lcbsd + gfss - interf.IN_BAND_INSERTION_LOSS grant['oobe_interference'] = oobe_interference
def computeInterferenceEsc(cbsd_grant, constraint, esc_antenna_info, max_eirp): """Computes interference that a grant causes to a ESC protection point. Routine to compute interference neighborhood grant causes to ESC protection point. Args: cbsd_grant: A CBSD grant of type |data.CbsdGrantInfo|. constraint: The protection constraint of type |data.ProtectionConstraint|. esc_antenna_info: ESC antenna information of type |data.EscInformation|. max_eirp: The maximum EIRP allocated to the grant during IAP procedure Returns: The interference contribution(dBm). """ # Get the propagation loss and incident angles for ESC entity db_loss, incidence_angles, _ = wf_itm.CalcItmPropagationLoss( cbsd_grant.latitude, cbsd_grant.longitude, cbsd_grant.height_agl, constraint.latitude, constraint.longitude, esc_antenna_info.antenna_height, cbsd_grant.indoor_deployment, reliability=-1, freq_mhz=FREQ_PROP_MODEL_MHZ) # Compute CBSD antenna gain in the direction of protection point ant_gain = antenna.GetStandardAntennaGains(incidence_angles.hor_cbsd, cbsd_grant.antenna_azimuth, cbsd_grant.antenna_beamwidth, cbsd_grant.antenna_gain) # Compute ESC antenna gain in the direction of CBSD esc_ant_gain = antenna.GetAntennaPatternGains( incidence_angles.hor_rx, esc_antenna_info.antenna_azimuth, esc_antenna_info.antenna_gain_pattern) # Get the total antenna gain by summing the antenna gains from CBSD to ESC # and ESC to CBSD effective_ant_gain = ant_gain + esc_ant_gain # Compute the interference value for ESC entity eirp = getEffectiveSystemEirp(max_eirp, cbsd_grant.antenna_gain, effective_ant_gain) interference = eirp - db_loss - getEscMaskLoss(constraint) return interference
def test_los_mode(self): lat1, lng1, height1 = 37.756672, -122.508512, 20.0 lat2, lng2, height2 = 37.756559, -122.507882, 10.0 reliability = 0.5 res = wf_itm.CalcItmPropagationLoss(lat1, lng1, height1, lat2, lng2, height2, reliability=reliability, freq_mhz=3625., return_internals=True) self.assertAlmostEqual(res.db_loss, 78.7408, 4) self.assertAlmostEqual(res.incidence_angles.ver_cbsd, -res.incidence_angles.ver_rx, 3) self.assertEqual(res.internals['itm_err_num'], wf_itm.ItmErrorCode.OTHER) # LOS mode
rels = [-1, 0.5] rt = "RURAL" lat2 = 32.74049506692264 lng2 = -117.14025198345544 agl2 = 1.5 for rel in rels: loss_hybrid = wf_hybrid.CalcHybridPropagationLoss(lat1, lng1, agl1, lat2, lng2, agl2, cbsd_indoor=indoor, region=rt, reliability=rel) loss_itm = wf_itm.CalcItmPropagationLoss(lat1, lng1, agl1, lat2, lng2, agl2, cbsd_indoor=indoor, reliability=rel) print "Reliablity: ", rel print "Hybrid Loss:", loss_hybrid[0], "dbm:", 26 - loss_hybrid[0] print "ITM Loss:", loss_itm[0], "dbm", 26 - loss_itm[0] print
def computePropagationAntennaModel(request): reliability_level = request['reliabilityLevel'] if reliability_level not in [-1, 0.05, 0.95]: raise ValueError('reliability_level not in [-1, 0.05, 0.95]') tx = request['cbsd'] if ('fss' in request) and ('ppa' in request): raise ValueError('fss and ppa in request') elif 'ppa' in request: rx = {} rx['height'] = 1.5 isfss = False coordinates = [] ppa = request['ppa'] arcsec = 1 ppa_points = geoutils.GridPolygon(ppa['geometry'], arcsec) if len(ppa_points) == 1: rx['longitude'] = ppa_points[0][0] rx['latitude'] = ppa_points[0][1] elif len(ppa_points) == 0: raise ValueError('ppa boundary contains no protection point') else: raise ValueError( 'ppa boundary contains more than a single protection point') region_val = drive.nlcd_driver.RegionNlcdVote( [[rx['latitude'], rx['longitude']]]) elif 'fss' in request: isfss = True rx = request['fss'] else: raise ValueError('Neither fss nor ppa in request') # ITM pathloss (if receiver type is FSS) or the hybrid model pathloss (if receiver type is PPA) and corresponding antenna gains. # The test specification notes that the SAS UUT shall use default values for w1 and w2 in the ITM model. result = {} if isfss: path_loss = wf_itm.CalcItmPropagationLoss( tx['latitude'], tx['longitude'], tx['height'], rx['latitude'], rx['longitude'], rx['height'], cbsd_indoor=tx['indoorDeployment'], reliability=reliability_level, freq_mhz=3625., is_height_cbsd_amsl=(tx['heightType'] == 'AMSL')) result['pathlossDb'] = path_loss.db_loss gain_tx_rx = antenna.GetStandardAntennaGains( path_loss.incidence_angles.hor_cbsd, ant_azimuth=tx['antennaAzimuth'], ant_beamwidth=tx['antennaBeamwidth'], ant_gain=tx['antennaGain']) result['txAntennaGainDbi'] = gain_tx_rx if 'rxAntennaGainRequired' in rx: hor_dirs = path_loss.incidence_angles.hor_rx ver_dirs = path_loss.incidence_angles.ver_rx gain_rx_tx = antenna.GetFssAntennaGains(hor_dirs, ver_dirs, rx['antennaAzimuth'], rx['antennaElevation'], rx['antennaGain']) result['rxAntennaGainDbi'] = gain_rx_tx else: path_loss = wf_hybrid.CalcHybridPropagationLoss( tx['latitude'], tx['longitude'], tx['height'], rx['latitude'], rx['longitude'], rx['height'], cbsd_indoor=tx['indoorDeployment'], reliability=-1, freq_mhz=3625., region=region_val, is_height_cbsd_amsl=(tx['heightType'] == 'AMSL')) result['pathlossDb'] = path_loss.db_loss gain_tx_rx = antenna.GetStandardAntennaGains( path_loss.incidence_angles.hor_cbsd, ant_azimuth=tx['antennaAzimuth'], ant_beamwidth=tx['antennaBeamwidth'], ant_gain=tx['antennaGain']) result['txAntennaGainDbi'] = gain_tx_rx return result
base_fss.latitude, base_fss.longitude, cbsd.latitude, cbsd.longitude) if distance_km <= 150: cbsds.append(cbsd) # - then calculate all path losses and received RSSI at FSS # (without FSS antenna taken into account) cbsd_rssi = np.zeros(len(cbsds)) cbsd_incidence_angles = [] for k, cbsd in enumerate(cbsds): if not (k%10): sys.stdout.write('*') sys.stdout.flush() db_loss, incidence_angles, _ = wf_itm.CalcItmPropagationLoss( cbsd.latitude, cbsd.longitude, cbsd.height_agl, base_fss.latitude, base_fss.longitude, base_fss.height_agl, cbsd.is_indoor, reliability=-1) cbsd_effective_eirp = antenna.GetStandardAntennaGains( incidence_angles.hor_cbsd, cbsd.antenna_azimuth, cbsd.antenna_beamwidth, cbsd.eirp_dbm_mhz) cbsd_rssi[k] = cbsd_effective_eirp - db_loss cbsd_incidence_angles.append(incidence_angles) # - then compute the aggregate average interference for each FSS entity # (ie each different possible FSS antenna pointing) fss_total_rssi = [] for fss_entity in fss_entities: hor_dirs = [inc_angle.hor_rx for inc_angle in cbsd_incidence_angles]
def computeInterference(grant, constraint, inc_ant_height, num_iteration, dpa_type): """Calculate interference contribution of each grant in the neighborhood to the protection constraint c. Inputs: cbsd_grant: a |data.CbsdGrantInfo| grant constraint: protection constraint of type |data.ProtectionConstraint| inc_ant_height: reference incumbent antenna height (in meters) num_iteration: a number of Monte Carlo iterations dpa_type: an enum member of class DpaType Returns: A tuple of interference: interference contribution, a tuple with named fields 'randomInterference' (K random interference contributions of the grant to protection constraint c), and 'bearing_c_cbsd' (bearing from c to CBSD grant location). medianInterference: the median interference. """ # Get frequency information low_freq_cbsd = grant.low_frequency high_freq_cbsd = grant.high_frequency low_freq_c = constraint.low_frequency high_freq_c = constraint.high_frequency # Compute median and K random realizations of path loss/interference contribution # based on ITM model as defined in [R2-SGN-03] (in dB) reliabilities = np.random.uniform(0.001, 0.999, num_iteration) # get K random # reliability values from an uniform distribution over [0.001,0.999) reliabilities = np.append(reliabilities, [0.5]) # add 0.5 (for median loss) as # a last value to reliabilities array results = wf_itm.CalcItmPropagationLoss(grant.latitude, grant.longitude, grant.height_agl, constraint.latitude, constraint.longitude, inc_ant_height, grant.indoor_deployment, reliability=reliabilities, freq_mhz=FREQ_PROP_MODEL) path_loss = np.array(results.db_loss) # Compute CBSD antenna gain in the direction of protection point ant_gain = antenna.GetStandardAntennaGains( results.incidence_angles.hor_cbsd, grant.antenna_azimuth, grant.antenna_beamwidth, grant.antenna_gain) # Compute EIRP of CBSD grant inside the frequency range of protection constraint if dpa_type is not DpaType.OUT_OF_BAND: # For co-channel offshore/inland DPAs # CBSD co-channel bandwidth overlapping with # frequency range of protection constraint co_channel_bw = min(high_freq_cbsd, high_freq_c) - max( low_freq_cbsd, low_freq_c) # Compute EIRP within co-channel bandwidth eirp_cbsd = ((grant.max_eirp - grant.antenna_gain) + ant_gain + 10 * np.log10(co_channel_bw / 1.e6)) else: # For out-of-band inland DPAs # Compute out-of-band conducted power oob_power = ComputeOOBConductedPower(low_freq_cbsd, low_freq_c, high_freq_c) # Compute out-of-band EIRP eirp_cbsd = oob_power + ant_gain # Calculate the interference contributions interf = eirp_cbsd - path_loss median_interf = interf[-1] # last element is the median interference K_interf = interf[:-1] # first 'K' interference # Store interference contributions interference = InterferenceContribution( randomInterference=K_interf, bearing_c_cbsd=results.incidence_angles.hor_rx) return interference, median_interf
def test_WINNF_FT_S_BPR_2(self, config_filename): """[Configurable] Grant Request from CBSDs within the Shared Zone adjacent the Canadian border. """ config = loadConfig(config_filename) # Very light checking of the config file. self.assertValidConfig( config, { 'registrationRequests': list, 'conditionalRegistrationData': list, 'grantRequests': list }) self.assertEqual( len(config['registrationRequests']), len(config['grantRequests'])) # Whitelist FCC ID and User ID. for device in config['registrationRequests']: self._sas_admin.InjectFccId({'fccId': device['fccId']}) self._sas_admin.InjectUserId({'userId': device['userId']}) # Pre-load conditional registration data. if config['conditionalRegistrationData']: self._sas_admin.PreloadRegistrationData({ 'registrationData': config['conditionalRegistrationData'] }) # Register CBSDs. request = {'registrationRequest': config['registrationRequests']} responses = self._sas.Registration(request)['registrationResponse'] # Check registration response. self.assertEqual(len(responses), len(config['registrationRequests'])) grant_request = [] index_of_devices_with_success_registration_response = [] for i, response in enumerate(responses): if response['response']['responseCode'] == 0: self.assertTrue('cbsdId' in response) index_of_devices_with_success_registration_response.append(i) config['grantRequests'][i]['cbsdId'] = response['cbsdId'] grant_request.append(config['grantRequests'][i]) del request, responses if not grant_request: return # SAS passes immediately, since none of the registration requests # succeeded in this case. # For CBSDs successfully registered in Step 1, Send grant request request = {'grantRequest': grant_request} responses = self._sas.Grant(request)['grantResponse'] # Check grant response self.assertEqual(len(responses), len(grant_request)) self.assertEqual( len(responses), len(index_of_devices_with_success_registration_response)) for i, index in enumerate( index_of_devices_with_success_registration_response): logging.info('Looking at Grant response number: %d', i) if (config['grantRequests'][i]['operationParam'] ['operationFrequencyRange']['highFrequency'] <= 3650e6): logging.info( 'Grant is not subject to Arrangement R because it does not overlap ' 'with 3650-3700 MHz.') continue if 'installationParam' in config['registrationRequests'][index]: cbsd_information = config['registrationRequests'][index] else: for conditional in config['conditionalRegistrationData']: if (config['registrationRequests'][index]['fccId'] == conditional['fccId']) and ( config['registrationRequests'][index]['cbsdSerialNumber'] == conditional['cbsdSerialNumber']): cbsd_information = conditional cbsd_lat = cbsd_information['installationParam']['latitude'] cbsd_lon = cbsd_information['installationParam']['longitude'] cbsd_ant_azi = cbsd_information['installationParam']['antennaAzimuth'] cbsd_ant_beamwidth = cbsd_information['installationParam'][ 'antennaBeamwidth'] # Check if CBSD is in the Border Sharing Zone, Get the closest point. (is_in_sharing_zone, closest_point_lat, closest_point_lon) = utils.CheckCbsdInBorderSharingZone( cbsd_lat, cbsd_lon, cbsd_ant_azi, cbsd_ant_beamwidth) logging.info('CBSD is in Border Sharing Zone?: %s', is_in_sharing_zone) if not is_in_sharing_zone: continue # CBSD not in the sharing zone; no PFD check is required. # Proceed with PFD calculation logging.info('Closest point in the border: Lat is %f', closest_point_lat) logging.info('Closest point in the border: Long is %f', closest_point_lon) # requested_eirp (p) p = config['grantRequests'][index]['operationParam']['maxEirp'] # Calculate PL cbsd_height = cbsd_information['installationParam']['height'] cbsd_height_type = cbsd_information['installationParam']['heightType'] is_cbsd_indoor = cbsd_information['installationParam']['indoorDeployment'] closest_point_height = 1.5 # According to the spec freq_mhz = 3625. # Always in all SAS propagation = wf_itm.CalcItmPropagationLoss( cbsd_lat, cbsd_lon, cbsd_height, closest_point_lat, closest_point_lon, closest_point_height, reliability=0.5, cbsd_indoor=is_cbsd_indoor, freq_mhz=freq_mhz, is_height_cbsd_amsl=(cbsd_height_type == 'AMSL')) pl = propagation.db_loss logging.info('Propagation - db_loss: %f', propagation.db_loss) bearing = propagation.incidence_angles.hor_cbsd logging.info('Bearing: %f', propagation.incidence_angles.hor_cbsd) # Calculate effective antenna gain max_ant_gain = cbsd_information['installationParam']['antennaGain'] ant_gain = antenna.GetStandardAntennaGains( bearing, cbsd_ant_azi, cbsd_ant_beamwidth, max_ant_gain) logging.info('Effective Antenna Gain: %f', ant_gain) # Calculate PFD where: # p = requested_eirp # effective_eirp = p - maxAntGain + antGain # PFD = effective_eirp - pl + 32.6 pfd = p - max_ant_gain + ant_gain - pl + 32.6 logging.info('Power Flex Density: %f dBm/m2/MHz', pfd) if pfd > -80: self.assertTrue(responses[i]['response']['responseCode'] == 400)
def CalcHybridPropagationLoss(lat_cbsd, lon_cbsd, height_cbsd, lat_rx, lon_rx, height_rx, cbsd_indoor=False, reliability=-1, freq_mhz=3625., region='RURAL', is_height_cbsd_amsl=False, return_internals=False): """Implements the Hybrid ITM/eHata NTIA propagation model. As specified by Winforum, see: R2-SGN-03, R2-SGN-04 through R2-SGN-10 and NTIA TR 15-517 Appendix A. Note that contrary to the ITM model, this function does not provide the possibility to retrieve a CDF of path losses, as it is not required in the intended use of that model (it shall be used currently only for protecting zones using average aggregated interference). Warning: Only 'reliability' values 0.5 (median) and -1 (average) are currently fully supported, as other values will not return the quantile when using internally the eHata model. Workaround is to apply it afterwards when the returned opcode=4, and using the standard deviation obtained with the 'GetEHataStdev()' routine. 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. freq_mhz: Frequency (MHz). Default is mid-point of band. reliability: Reliability. Default is -1 (average value). Options: Value in [0,1]: returns the CDF quantile -1: returns the mean path loss region: Region type among 'URBAN', 'SUBURBAN, 'RURAL' is_height_cbsd_amsl: If True, the CBSD height shall be considered as AMSL (Average mean sea level). Returns: A namedtuple of: db_loss: Path Loss in dB. incidence_angles: A namedtuple of angles (degrees) 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): hybrid_opcode: Opcode from HybridCode - See GetInfoOnHybridCodes() effective_height_cbsd: Effective CBSD antenna height itm_db_loss: Loss in dB for the ITM model. itm_err_num: ITM error code (see wf_itm module). itm_str_mode: Description (string) of dominant prop mode in ITM. 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, incidence_angles=_IncidenceAngles(0, 0, 0, 0), internals=None) # Sanity checks on input parameters if freq_mhz < 40 or freq_mhz > 10000: raise Exception('Frequency outside range [40MHz - 10GHz].') if region not in ['RURAL', 'URBAN', 'SUBURBAN']: raise Exception('Region %s not allowed' % region) if reliability not in (-1, 0.5): raise Exception('Hybrid model only computes the median or the mean.') if is_height_cbsd_amsl: altitude_cbsd = drive.terrain_driver.GetTerrainElevation( lat_cbsd, lon_cbsd) height_cbsd = height_cbsd - altitude_cbsd # Get the terrain profile, using Vincenty great circle route, and WF # standard (bilinear interp; 1501 pts for all distances over 45 km) 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) # Structural CBSD and mobile height corrections height_cbsd = max(height_cbsd, 20.) height_rx = 1.5 # Calculate the predicted ITM loss # and get the distance and profile for use in further logic db_loss_itm, incidence_angles, internals = wf_itm.CalcItmPropagationLoss( lat_cbsd, lon_cbsd, height_cbsd, lat_rx, lon_rx, height_rx, False, reliability, freq_mhz, its_elev, return_internals=True) internals['itm_db_loss'] = db_loss_itm # Calculate the effective heights of the tx height_cbsd_eff = ehata.CbsdEffectiveHeights(height_cbsd, its_elev) internals['effective_height_cbsd'] = height_cbsd_eff # Use ITM if CBSD effective height greater than 200 m if height_cbsd_eff >= 200: return _BuildOutput(db_loss_itm, incidence_angles, internals, HybridMode.ITM_HIGH_HEIGHT, cbsd_indoor) # Set the environment code number. if region == 'URBAN': region_code = 23 elif region == 'SUBURBAN': region_code = 22 else: # 'RURAL': use ITM if not return_internals: return_internals = None return _BuildOutput(db_loss_itm, incidence_angles, internals, HybridMode.ITM_RURAL, cbsd_indoor) # The eHata offset to apply (only in case the mean is requested) offset_median_to_mean = _GetMedianToMeanOffsetDb(freq_mhz, region == 'URBAN') # Now process the different cases dist_km = internals['dist_km'] if not return_internals: return_internals = None if dist_km <= 0.1: # Use Free Space Loss db_loss = CalcFreeSpaceLoss(dist_km, freq_mhz, height_cbsd, height_rx) return _BuildOutput(db_loss, incidence_angles, internals, HybridMode.FSL, cbsd_indoor) elif dist_km > 0.1 and dist_km < 1: # Use E-Hata Median Basic Prop Loss fsl_100m = CalcFreeSpaceLoss(0.1, freq_mhz, height_cbsd, height_rx) median_basic_loss = ehata.MedianBasicPropLoss(freq_mhz, height_cbsd, height_rx, 1, region_code) alpha = 1. + math.log10(dist_km) db_loss = fsl_100m + alpha * (median_basic_loss - fsl_100m) # TODO: validate the following approach with WinnForum participants: # Weight the offset as well from 0 (100m) to 1.0 (1km). if reliability == -1: db_loss += alpha * offset_median_to_mean return _BuildOutput(db_loss, incidence_angles, internals, HybridMode.EHATA_FSL_INTERP, cbsd_indoor) elif dist_km >= 1 and dist_km <= 80: # Use best of E-Hata / ITM ehata_loss_med = ehata.ExtendedHata(its_elev, freq_mhz, height_cbsd, height_rx, region_code) if reliability == 0.5: ehata_loss = ehata_loss_med itm_loss_med = db_loss_itm else: ehata_loss = ehata_loss_med + offset_median_to_mean itm_loss_med = wf_itm.CalcItmPropagationLoss( lat_cbsd, lon_cbsd, height_cbsd, lat_rx, lon_rx, height_rx, False, 0.5, freq_mhz, its_elev).db_loss if itm_loss_med >= ehata_loss_med: return _BuildOutput(db_loss_itm, incidence_angles, internals, HybridMode.ITM_DOMINANT, cbsd_indoor) else: return _BuildOutput(ehata_loss, incidence_angles, internals, HybridMode.EHATA_DOMINANT, cbsd_indoor) elif dist_km > 80: # Use the ITM with correction from E-Hata @ 80km # Calculate the ITM median and eHata median losses at 80km bearing = incidence_angles.hor_cbsd lat_80km, lon_80km, _ = vincenty.GeodesicPoint(lat_cbsd, lon_cbsd, 80., bearing) its_elev_80km = drive.terrain_driver.TerrainProfile( lat_cbsd, lon_cbsd, lat_80km, lon_80km, target_res_meter=30., do_interp=True, max_points=1501) ehata_loss_80km = ehata.ExtendedHata(its_elev_80km, freq_mhz, height_cbsd, height_rx, region_code) itm_loss_80km = wf_itm.CalcItmPropagationLoss(lat_cbsd, lon_cbsd, height_cbsd, lat_80km, lon_80km, height_rx, False, 0.5, freq_mhz, its_elev_80km).db_loss J = max(ehata_loss_80km - itm_loss_80km, 0) db_loss = db_loss_itm + J return _BuildOutput(db_loss, incidence_angles, internals, HybridMode.ITM_CORRECTED, cbsd_indoor)
def CalcItm(rel, rx_lat, rx_lng): res = wf_itm.CalcItmPropagationLoss(cbsd_lat, cbsd_lon, 10, rx_lat, rx_lng, 1.5, False, rel, freq_mhz) return res
def test_WINNF_FT_S_QPR_5(self, config_filename): """[Configurable] Unsuccessful Grant Request from CBSDs within Coordination Area around Table Mountain Quiet Zone (QZ) with Multiple Grants. """ config = loadConfig(config_filename) self.assertValidConfig( config, { 'registrationRequests': list, 'conditionalRegistrationData': list, 'grantRequestsN1': list, 'grantRequestsN2': list }) self.assertEqual(len(config['registrationRequests']), len(config['grantRequestsN1'])) self.assertEqual(len(config['grantRequestsN1']), len(config['grantRequestsN2'])) # Whitelist FCC ID and User ID. for device in config['registrationRequests']: self._sas_admin.InjectFccId({'fccId': device['fccId']}) self._sas_admin.InjectUserId({'userId': device['userId']}) # Pre-load conditional registration data. if config['conditionalRegistrationData']: self._sas_admin.PreloadRegistrationData( {'registrationData': config['conditionalRegistrationData']}) # Step 1: Register CBSDs. request = {'registrationRequest': config['registrationRequests']} responses = self._sas.Registration(request)['registrationResponse'] # Check registration response. self.assertEqual(len(responses), len(config['registrationRequests'])) grant_request_n1 = [] grant_request_n2 = [] successful_reg_requests = [] for i, response in enumerate(responses): if response['response']['responseCode'] == 0: self.assertTrue('cbsdId' in response) successful_reg_requests.append( config['registrationRequests'][i]) config['grantRequestsN1'][i]['cbsdId'] = response['cbsdId'] grant_request_n1.append(config['grantRequestsN1'][i]) config['grantRequestsN2'][i]['cbsdId'] = response['cbsdId'] grant_request_n2.append(config['grantRequestsN2'][i]) del request, responses if not successful_reg_requests: return # SAS passes immediately, since none of the registration requests # succeeded in this case. # Step 2: For CBSDs successfully registered in Step 1, Send grant request 1 request1 = {'grantRequest': grant_request_n1} grant_responses1 = self._sas.Grant(request1)['grantResponse'] # Check grant response 1 self.assertEqual(len(grant_responses1), len(grant_request_n1)) # Step 3: For CBSDs successfully registered in Step 1, Send grant request 2 request2 = {'grantRequest': grant_request_n2} grant_responses2 = self._sas.Grant(request2)['grantResponse'] # Check grant response 2 self.assertEqual(len(grant_responses2), len(grant_request_n2)) for i, device in enumerate(successful_reg_requests): if 'installationParam' in device: cbsd_information = device else: for conditional in config['conditionalRegistrationData']: if (device['fccId'] == conditional['fccId']) and ( device['cbsdSerialNumber'] == conditional['cbsdSerialNumber']): cbsd_information = conditional logging.info( 'Looking at device with FccID: %s / CbsdSerialNumber: %s', cbsd_information['fccId'], cbsd_information['cbsdSerialNumber']) grant_response1 = grant_responses1[i] grant_response2 = grant_responses2[i] if not (grant_response1['response']['responseCode'] == 0 or grant_response2['response']['responseCode'] == 0): logging.info( 'Both grant requests were rejected for this device.') continue # Skip further calculation for this device. # Calculate PL logging.info( 'Calculating PL for device with FccID: %s / CbsdSerialNumber: %s', cbsd_information['fccId'], cbsd_information['cbsdSerialNumber']) cbsd_lat = cbsd_information['installationParam']['latitude'] cbsd_lon = cbsd_information['installationParam']['longitude'] cbsd_ant_azi = cbsd_information['installationParam'][ 'antennaAzimuth'] cbsd_ant_beamwidth = cbsd_information['installationParam'][ 'antennaBeamwidth'] cbsd_height = cbsd_information['installationParam']['height'] cbsd_height_type = cbsd_information['installationParam'][ 'heightType'] is_cbsd_indoor = cbsd_information['installationParam'][ 'indoorDeployment'] freq_mhz = 3625. # Always in all SAS table_mountain_quiet_zone_lat = 40.130660 table_mountain_quiet_zone_long = -105.244596 table_mountain_quiet_zone_height = 9 # 9 m: According to the spec propagation = wf_itm.CalcItmPropagationLoss( cbsd_lat, cbsd_lon, cbsd_height, table_mountain_quiet_zone_lat, table_mountain_quiet_zone_long, table_mountain_quiet_zone_height, cbsd_indoor=is_cbsd_indoor, reliability=0.5, freq_mhz=freq_mhz, is_height_cbsd_amsl=(cbsd_height_type == 'AMSL')) pl = propagation.db_loss logging.info('Propagation:db_loss: %f', pl) bearing = propagation.incidence_angles.hor_cbsd logging.info('Bearing: %f', bearing) # Calculate effective antenna gain max_ant_gain_dbi = cbsd_information['installationParam'][ 'antennaGain'] ant_gain_dbi = antenna.GetStandardAntennaGains( bearing, cbsd_ant_azi, cbsd_ant_beamwidth, max_ant_gain_dbi) logging.info('Effective Antenna Gain: %f', ant_gain_dbi) # Gather values required for calculating Total Interference grant1_eirp = 0 if grant_response1['response']['responseCode'] == 0: p1 = grant_request_n1[i]['operationParam']['maxEirp'] logging.info('Grant 1 Max Eirp: %f dBm/MHz', p1) bw1 = (grant_request_n1[i]['operationParam'] ['operationFrequencyRange']['highFrequency'] - grant_request_n1[i]['operationParam'] ['operationFrequencyRange']['lowFrequency']) / 1.e6 logging.info('Grant 1 Bandwidth: %f', bw1) grant1_eirp = (10**(p1 / 10.0)) * bw1 logging.info('Grant 1 EIRP is %f', grant1_eirp) grant2_eirp = 0 if grant_response2['response']['responseCode'] == 0: p2 = grant_request_n2[i]['operationParam']['maxEirp'] logging.info('Grant 2 Max Eirp: %f dBm/MHz', p2) bw2 = (grant_request_n2[i]['operationParam'] ['operationFrequencyRange']['highFrequency'] - grant_request_n2[i]['operationParam'] ['operationFrequencyRange']['lowFrequency']) / 1.e6 logging.info('Grant 2 Bandwidth: %f', bw2) grant2_eirp = (10**(p2 / 10.0)) * bw2 logging.info('Grant 2 EIRP is %f', grant2_eirp) # Step 4: Calculate Total Interference total_interference_dbm = ant_gain_dbi - max_ant_gain_dbi + ( 10 * np.log10(grant1_eirp + grant2_eirp)) - pl logging.info('Total Interference is %f dBm', total_interference_dbm) # CHECK: Total Interference from all approved grants is <= -88.4 dBm self.assertLessEqual(total_interference_dbm, -88.4)
def test_same_location(self): result = wf_itm.CalcItmPropagationLoss(45, -80, 10, 45, -80, 10) self.assertEqual(result.db_loss, 0) self.assertTupleEqual(result.incidence_angles, (0, 0, 0, 0))