예제 #1
0
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
예제 #2
0
def computeInterferencePpaGwpzPoint(cbsd_grant,
                                    constraint,
                                    h_inc_ant,
                                    max_eirp,
                                    region_type='SUBURBAN'):
    """Computes interference that a grant causes to GWPZ or PPA protection area.

  Routine to compute interference neighborhood grant causes to protection
  point within GWPZ or PPA protection area.

  Args:
    cbsd_grant: A CBSD grant of type |data.CbsdGrantInfo|.
    constraint: The protection constraint of type |data.ProtectionConstraint|.
    h_inc_ant: The reference incumbent antenna height (in meters).
    max_eirp: The maximum EIRP allocated to the grant during IAP procedure
    region_type: Region type of the GWPZ or PPA area:
                    'URBAN', 'SUBURBAN' or 'RURAL'.
  Returns:
    The interference contribution (dBm).
  """
    # Get the propagation loss and incident angles for area entity
    db_loss, incidence_angles, _ = wf_hybrid.CalcHybridPropagationLoss(
        cbsd_grant.latitude,
        cbsd_grant.longitude,
        cbsd_grant.height_agl,
        constraint.latitude,
        constraint.longitude,
        h_inc_ant,
        cbsd_grant.indoor_deployment,
        reliability=-1,
        freq_mhz=FREQ_PROP_MODEL_MHZ,
        region=region_type)

    # 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)

    # Get the exact overlap of the grant over the GWPZ area channels
    if constraint.entity_type == data.ProtectedEntityType.GWPZ_AREA:
        grant_overlap_bandwidth = min(cbsd_grant.high_frequency, constraint.high_frequency) \
            - max(cbsd_grant.low_frequency, constraint.low_frequency)
    else:
        grant_overlap_bandwidth = RBW_HZ

    # Get the interference value for area entity
    eirp = getEffectiveSystemEirp(max_eirp, cbsd_grant.antenna_gain, ant_gain,
                                  grant_overlap_bandwidth)

    interference = eirp - db_loss
    return interference
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
예제 #4
0
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
예제 #5
0
def _GetPolygon(device):
    """Returns the PPA contour for a single CBSD device, as a shapely polygon."""
    install_param = device['installationParam']
    eirp_capability = install_param.get(
        'eirpCapability', MAX_ALLOWABLE_EIRP_PER_10_MHZ_CAT_A if
        device['cbsdCategory'] == 'A' else MAX_ALLOWABLE_EIRP_PER_10_MHZ_CAT_B)

    # Compute all the Points in 0-359 every 200m up to 40km
    distances = np.arange(0.2, 40.1, 0.2)
    azimuths = np.arange(0.0, 360.0)
    latitudes, longitudes, _ = zip(*[
        vincenty.GeodesicPoints(install_param['latitude'],
                                install_param['longitude'], distances, azimuth)
        for azimuth in azimuths
    ])
    # Compute the Gain for all Direction
    # Note: some parameters are optional for catA, so falling back to None (omni) then
    antenna_gains = antenna.GetStandardAntennaGains(
        azimuths, install_param['antennaAzimuth'] if 'antennaAzimuth'
        in install_param else None, install_param['antennaBeamwidth']
        if 'antennaBeamwidth' in install_param else None,
        install_param['antennaGain'])
    # Get the Nlcd Region Type for Cbsd
    cbsd_region_code = drive.nlcd_driver.GetLandCoverCodes(
        install_param['latitude'], install_param['longitude'])
    cbsd_region_type = nlcd.GetRegionType(cbsd_region_code)
    # Compute the Path Loss, and contour based on Gain and Path Loss Comparing with Threshold
    # Smoothing Contour using Hamming Filter
    contour_dists_km = _HammingFilter([
        _CalculateDbLossForEachPointAndGetContour(install_param,
                                                  eirp_capability, ant_gain,
                                                  cbsd_region_type,
                                                  radial_lats, radial_lons)
        for radial_lats, radial_lons, ant_gain in zip(latitudes, longitudes,
                                                      antenna_gains)
    ])
    # Generating lat, lon for Contour
    contour_lats, contour_lons, _ = zip(*[
        vincenty.GeodesicPoint(install_param['latitude'],
                               install_param['longitude'], dists, az)
        for dists, az in zip(contour_dists_km, azimuths)
    ])

    return sgeo.Polygon(zip(contour_lons, contour_lats)).buffer(0)
예제 #6
0
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
#  - 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]
  ver_dirs = [inc_angle.ver_rx for inc_angle in cbsd_incidence_angles]

  fss_ant_gains =  antenna.GetFssAntennaGains(
      hor_dirs, ver_dirs,
      fss_entity.pointing_azimuth,
예제 #8
0
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 test_WINNF_FT_S_QPR_8(self, config_filename):
        """[Configurable] Unsuccessful Grant Request from CBSDs within 4.8 km of
      the FCC Field Offices with multiple grants.
    """
        config = loadConfig(config_filename)
        self.assertValidConfig(
            config, {
                'registrationRequest': dict,
                'conditionalRegistrationData': list,
                'grantRequest1': dict,
                'grantRequest2': dict
            })

        # Whitelist FCC ID and User ID.
        self._sas_admin.InjectFccId(
            {'fccId': config['registrationRequest']['fccId']})
        self._sas_admin.InjectUserId(
            {'userId': config['registrationRequest']['userId']})

        # Pre-load conditional registration data.
        if config['conditionalRegistrationData']:
            self._sas_admin.PreloadRegistrationData(
                {'registrationData': config['conditionalRegistrationData']})

        # Step 1: Register CBSD.
        request = {'registrationRequest': [config['registrationRequest']]}
        response = self._sas.Registration(request)['registrationResponse']

        # Check registration response.
        self.assertEqual(len(response), 1)
        if response[0]['response']['responseCode'] != 0:
            return  # SAS passes immediately in this case.
        cbsd_id = response[0]['cbsdId']
        del request, response

        # Step 2: Request first grant.
        config['grantRequest1']['cbsdId'] = cbsd_id
        grant_request = config['grantRequest1']
        request = {'grantRequest': [grant_request]}
        response = self._sas.Grant(request)['grantResponse'][0]
        # Check if the first grant response is successful.
        grant1_approved = response['response']['responseCode'] == 0
        del request, response

        # Step 3: Request second grant
        config['grantRequest2']['cbsdId'] = cbsd_id
        grant_request = config['grantRequest2']
        request = {'grantRequest': [grant_request]}
        response = self._sas.Grant(request)['grantResponse'][0]
        # Check if the second grant response is successful.
        grant2_approved = response['response']['responseCode'] == 0
        del request, response

        if not (grant1_approved or grant2_approved):
            logging.info('Both grant requests were rejected')
            return  # SAS passes immediately in this case.

        # Calculate the closest FCC office
        lat_cbsd = config['conditionalRegistrationData'][0][
            'installationParam']['latitude']
        lon_cbsd = config['conditionalRegistrationData'][0][
            'installationParam']['longitude']
        distance_offices = [
            vincenty.GeodesicDistanceBearing(lat_cbsd, lon_cbsd,
                                             office['latitude'],
                                             office['longitude'])[0]
            for office in self.fcc_offices
        ]
        index_closest = np.argmin(distance_offices)
        closest_fcc_office = self.fcc_offices[index_closest]
        logging.info('Closest FCC Office Lat: %f',
                     closest_fcc_office['latitude'])
        logging.info('Closest FCC Office Long: %f',
                     closest_fcc_office['longitude'])
        # Calculate bearing and ant_gain
        _, bearing, _ = vincenty.GeodesicDistanceBearing(
            lat_cbsd, lon_cbsd, closest_fcc_office['latitude'],
            closest_fcc_office['longitude'])
        logging.info('Bearing: %f', bearing)
        ant_gain_dbi = antenna.GetStandardAntennaGains(
            bearing, config['conditionalRegistrationData'][0]
            ['installationParam']['antennaAzimuth'],
            config['conditionalRegistrationData'][0]['installationParam']
            ['antennaBeamwidth'], config['conditionalRegistrationData'][0]
            ['installationParam']['antennaGain'])
        logging.info('Ant Gain: %f dBi', ant_gain_dbi)
        max_ant_gain_dbi = (config['conditionalRegistrationData'][0]
                            ['installationParam']['antennaGain'])
        logging.info('Max Ant Gain: %f dBi', max_ant_gain_dbi)

        # Gather values required for calculating Total EIRP
        grant1_eirp = 0
        if grant1_approved:
            p1 = config['grantRequest1']['operationParam']['maxEirp']
            logging.info('Grant 1 Max Eirp: %f dBm/MHz', p1)
            bw1 = (config['grantRequest1']['operationParam']
                   ['operationFrequencyRange']['highFrequency'] -
                   config['grantRequest1']['operationParam']
                   ['operationFrequencyRange']['lowFrequency']) / 1.e6
            logging.info('Grant 1 Bandwidth: %f', bw1)
            grant1_eirp = (10**(p1 / 10.0)) * bw1
            logging.info('Grant 1 nominal EIRP is %f', grant1_eirp)

        grant2_eirp = 0
        if grant2_approved:
            p2 = config['grantRequest2']['operationParam']['maxEirp']
            logging.info('Grant 2 Max Eirp: %f dBm/MHz', p2)
            bw2 = (config['grantRequest2']['operationParam']
                   ['operationFrequencyRange']['highFrequency'] -
                   config['grantRequest2']['operationParam']
                   ['operationFrequencyRange']['lowFrequency']) / 1.e6
            logging.info('Grant 2 Bandwidth: %f', bw2)
            grant2_eirp = (10**(p2 / 10.0)) * bw2
            logging.info('Grant 2 nominal EIRP is %f', grant2_eirp)

        # Step 4: Calculate Total EIRP
        total_eirp_dbm = ant_gain_dbi - max_ant_gain_dbi + (
            10 * np.log10(grant1_eirp + grant2_eirp))
        logging.info('Total EIRP is %f dBm', total_eirp_dbm)

        # CHECK: Total EIRP of all approved grants is <= 49.15 dBm
        self.assertLessEqual(total_eirp_dbm, 49.15)
    def test_WINNF_FT_S_QPR_7(self, config_filename):
        """[Configurable] Unsuccessful Grant Request from CBSDs within 4.8 km of
      the FCC Field Offices.
    """
        config = loadConfig(config_filename)
        # Very light checking of the config file.
        self.assertValidConfig(
            config, {
                'registrationRequest': dict,
                'conditionalRegistrationData': list,
                'grantRequest': dict
            })

        # Whitelist FCC ID and User ID.
        self._sas_admin.InjectFccId(
            {'fccId': config['registrationRequest']['fccId']})
        self._sas_admin.InjectUserId(
            {'userId': config['registrationRequest']['userId']})

        # Pre-load conditional registration data.
        if config['conditionalRegistrationData']:
            self._sas_admin.PreloadRegistrationData(
                {'registrationData': config['conditionalRegistrationData']})

        # Register CBSD.
        request = {'registrationRequest': [config['registrationRequest']]}
        response = self._sas.Registration(request)['registrationResponse']

        # Check registration response.
        self.assertEqual(len(response), 1)
        if response[0]['response']['responseCode'] != 0:
            return  # SAS passes immediately in this case.
        cbsd_id = response[0]['cbsdId']
        del request, response

        # Calculate the closest FCC office
        lat_cbsd = config['conditionalRegistrationData'][0][
            'installationParam']['latitude']
        lon_cbsd = config['conditionalRegistrationData'][0][
            'installationParam']['longitude']
        distance_offices = [
            vincenty.GeodesicDistanceBearing(lat_cbsd, lon_cbsd,
                                             office['latitude'],
                                             office['longitude'])[0]
            for office in self.fcc_offices
        ]
        index_closest = np.argmin(distance_offices)
        closest_fcc_office = self.fcc_offices[index_closest]
        logging.info('Closest FCC office Lat: %f',
                     closest_fcc_office['latitude'])
        logging.info('Closest FCC office Long: %f',
                     closest_fcc_office['longitude'])
        # Calculate bearing and ant_gain
        _, bearing, _ = vincenty.GeodesicDistanceBearing(
            lat_cbsd, lon_cbsd, closest_fcc_office['latitude'],
            closest_fcc_office['longitude'])
        ant_gain = antenna.GetStandardAntennaGains(
            bearing, config['conditionalRegistrationData'][0]
            ['installationParam']['antennaAzimuth'],
            config['conditionalRegistrationData'][0]['installationParam']
            ['antennaBeamwidth'], config['conditionalRegistrationData'][0]
            ['installationParam']['antennaGain'])
        logging.info('ant_gain is %f dBi', ant_gain)
        # Gather values required for calculating EIRP
        p = config['grantRequest']['operationParam']['maxEirp']
        logging.info('Grant maxEirp is %f', p)
        max_ant_gain = (config['conditionalRegistrationData'][0]
                        ['installationParam']['antennaGain'])
        logging.info('max_ant_gain is %f dBi', max_ant_gain)
        bw = (config['grantRequest']['operationParam']
              ['operationFrequencyRange']['highFrequency'] -
              config['grantRequest']['operationParam']
              ['operationFrequencyRange']['lowFrequency']) / 1.e6
        logging.info('bw is %f MHz', bw)
        # Calculate EIRP to verify grant response
        eirp = (p - max_ant_gain + ant_gain + (10 * np.log10(bw)))
        logging.info('EIRP is %f dBm', eirp)

        # If successfully registered, CBSD sends a grant request
        config['grantRequest']['cbsdId'] = cbsd_id
        grant_request = config['grantRequest']
        request = {'grantRequest': [grant_request]}
        response = self._sas.Grant(request)['grantResponse']
        # Check grant response
        self.assertEqual(len(response), 1)
        # If EIRP <= 49.15 dBm = SUCCESS
        if eirp <= 49.15:
            self.assertEqual(response[0]['response']['responseCode'], 0)
        # If EIRP > 49.15 dBm = INTERFERENCE
        else:
            self.assertEqual(response[0]['response']['responseCode'], 400)
    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_standard_gain(self):
        # Isotropic antenna
        gains = antenna.GetStandardAntennaGains([0, 90, 180, 270], 0, None, 5)
        self.assertEqual(np.max(np.abs(gains - 5 * np.ones(4))), 0)
        gains = antenna.GetStandardAntennaGains([0, 90, 180, 270], None, 90, 5)
        self.assertEqual(np.max(np.abs(gains - 5 * np.ones(4))), 0)
        gains = antenna.GetStandardAntennaGains([0, 90, 180, 270], 0, 0, 5)
        self.assertEqual(np.max(np.abs(gains - 5 * np.ones(4))), 0)
        gains = antenna.GetStandardAntennaGains([0, 90, 180, 270], 0, 360, 5)
        self.assertEqual(np.max(np.abs(gains - 5 * np.ones(4))), 0)

        # Back lobe: maximum attenuation
        gain = antenna.GetStandardAntennaGains(180, 0, 120, 10)
        self.assertEqual(gain, -10)
        # At beamwidth, cutoff by 3dB (by definition)
        gain = antenna.GetStandardAntennaGains(60, 0, 120, 10)
        self.assertEqual(gain, 10 - 3)
        gain = antenna.GetStandardAntennaGains(5.5, 50.5, 90, 10)
        self.assertEqual(gain, 10 - 3)
        # Bore sight: full gain
        gain = antenna.GetStandardAntennaGains(50.5, 50.5, 90, 10)
        self.assertEqual(gain, 10)
        # At half beamwidth, -0.75dB + integer values well managed
        gain = antenna.GetStandardAntennaGains(25, 50, 100, 10)
        self.assertEqual(gain, 10 - 0.75)
        # At twice beamwidth, -12dB
        gain = antenna.GetStandardAntennaGains(310, 50, 100, 10)
        self.assertEqual(gain, 10 - 12)

        # Vectorized vs scalar
        hor_dirs = [3.5, 47.3, 342]
        gains = antenna.GetStandardAntennaGains(hor_dirs, 123.3, 90, 12.4)
        for k, hor in enumerate(hor_dirs):
            gain = antenna.GetStandardAntennaGains(hor, 123.3, 90, 12.4)
            self.assertEqual(gain, gains[k])