Example #1
0
    def CalcKeepListInterference(self, channel, num_iter=None):
        """Calculates max aggregate interference per protected point.

    Args:
      channel: A channel as tuple (low_freq_mhz, high_freq_mhz).
      num_iter: The number of Monte Carlo iteration when calculating the aggregate
        interference.

    Returns:
      The 95% aggregate interference per protected point, as a list.
      Each element is the maximum aggregate interference over all radar directions.
    """
        if num_iter is None:
            num_iter = Dpa.num_iteration
        keep_list = self.GetKeepList(channel)
        interfCalculator = functools.partial(
            ml.calcAggregatedInterference,
            low_freq=channel[0] * 1e6,
            high_freq=channel[1] * 1e6,
            grants=keep_list,
            inc_ant_height=self.radar_height,
            num_iter=num_iter,
            beamwidth=self.beamwidth,
            min_azimuth=self.azimuth_range[0],
            max_azimuth=self.azimuth_range[1],
            neighbor_distances=self.neighbor_distances,
            do_max=True)

        pool = mpool.Pool()
        max_interf = pool.map(interfCalculator, self.protected_points)
        return max_interf
    def ComputeMoveLists(self):
        """Computes move/neighbor lists.

    This routine updates the internal grants move list and neighbor list.
    One set of list is maintained per protected channel.
    To retrieve the list, see the routines GetMoveList(), GetNeighborList() and
    GetKeepList().
    """
        logging.info(
            'DPA Compute movelist `%s`- channels %s thresh %s bw %s height %s '
            'iter %s azi_range %s nbor_dists %s', self.name, self._channels,
            self.threshold, self.beamwidth, self.radar_height,
            Dpa.num_iteration, self.azimuth_range, self.neighbor_distances)
        logging.debug('  protected points: %s', self.protected_points)
        pool = mpool.Pool()
        self.ResetLists()
        # Detect the inside "inside grants", which will allow to
        # add them into move list for sure later on.
        inside_grants = set()
        if self.geometry and not isinstance(self.geometry, sgeo.Point):
            inside_grants = set(g for g in self._grants if sgeo.Point(
                g.longitude, g.latitude).intersects(self.geometry))

        for chan_idx, (low_freq, high_freq) in enumerate(self._channels):
            moveListConstraint = functools.partial(
                ml.moveListConstraint,
                low_freq=low_freq * 1.e6,
                high_freq=high_freq * 1.e6,
                grants=self._grants,
                inc_ant_height=self.radar_height,
                num_iter=Dpa.num_iteration,
                threshold=self.threshold,
                beamwidth=self.beamwidth,
                min_azimuth=self.azimuth_range[0],
                max_azimuth=self.azimuth_range[1],
                neighbor_distances=self.neighbor_distances)

            move_list, nbor_list = zip(
                *pool.map(moveListConstraint, self.protected_points))
            # Combine the individual point move lists
            move_list = set().union(*move_list)
            nbor_list = set().union(*nbor_list)
            include_grants = ml.filterGrantsForFreqRange(
                inside_grants, low_freq * 1.e6, high_freq * 1.e6)
            move_list.update(include_grants)
            nbor_list.update(include_grants)
            self.move_lists[chan_idx] = move_list
            self.nbor_lists[chan_idx] = nbor_list

        logging.info('DPA Result movelist `%s`- MOVE_LIST:%s NBOR_LIST: %s',
                     self.name, self.move_lists, self.nbor_lists)
def calculateAggregateInterferenceForGwpz(gwpz_record, grants):
    """Calculates per-channel aggregate interference for GWPZ.

  Args:
    gwpz_record: A GWPZ record dict.
    grants: An iterable of CBSD grants of type |data.CbsdGrantInfo|.

  Returns:
    Aggregate interference to GWPZ in the nested dictionary format.
      {latitude : {longitude: [aggr_interf1(mW), ..., aggr_interfK(mW)]}}
    The list contains the value per protected channel.
  """
    gwpz_region = gwpz_record['landCategory']

    # Get Fine Grid Points for a GWPZ protection area
    protection_points = utils.GridPolygon(
        gwpz_record['zone']['features'][0]['geometry'], GWPZ_GRID_RES_ARCSEC)
    gwpz_freq_range = gwpz_record['record']['deploymentParam'][0]\
                                 ['operationParam']['operationFrequencyRange']
    gwpz_low_freq = gwpz_freq_range['lowFrequency']
    gwpz_high_freq = gwpz_freq_range['highFrequency']

    # Get channels over which area incumbent needs partial/full protection
    protection_channels = interf.getProtectedChannels(gwpz_low_freq,
                                                      gwpz_high_freq)

    # Calculate aggregate interference from each protection constraint with a
    # pool of parallel processes.
    logging.info(
        'Computing aggregateInterferenceForPoint for PPA (%s), channels (%s), '
        'nPoints (%d), grants (%s), region_type (%s)', gwpz_record,
        protection_channels, len(protection_points), grants, gwpz_region)
    logging.debug('  points: %s', protection_points)

    interfCalculator = partial(
        aggregateInterferenceForPoint,
        channels=protection_channels,
        grants=grants,
        fss_info=None,
        esc_antenna_info=None,
        protection_ent_type=data.ProtectedEntityType.GWPZ_AREA,
        region_type=gwpz_region)

    pool = mpool.Pool()
    interferences = pool.map(interfCalculator, protection_points)
    return InterferenceDict(interferences)
Example #4
0
    def ComputeMoveLists(self):
        """Computes move/neighbor lists.

    This routine updates the internal grants move list and neighbor list.
    One set of list is maintained per protected channel.
    To retrieve the list, see the routines GetMoveList(), GetNeighborList() and
    GetKeepList().
    """
        logging.info(
            'DPA Compute movelist `%s`- channels %s thresh %s bw %s height %s '
            'iter %s azi_range %s nbor_dists %s', self.name, self._channels,
            self.threshold, self.beamwidth, self.radar_height,
            Dpa.num_iteration, self.azimuth_range, self.neighbor_distances)
        logging.debug('  protected points: %s', self.protected_points)
        pool = mpool.Pool()
        self.ResetLists()
        for chan_idx, (low_freq, high_freq) in enumerate(self._channels):
            moveListConstraint = functools.partial(
                ml.moveListConstraint,
                low_freq=low_freq * 1.e6,
                high_freq=high_freq * 1.e6,
                grants=self._grants,
                inc_ant_height=self.radar_height,
                num_iter=Dpa.num_iteration,
                threshold=self.threshold,
                beamwidth=self.beamwidth,
                min_azimuth=self.azimuth_range[0],
                max_azimuth=self.azimuth_range[1],
                neighbor_distances=self.neighbor_distances)

            move_list, nbor_list = zip(
                *pool.map(moveListConstraint, self.protected_points))
            # Combine the individual point move lists
            move_list = set().union(*move_list)
            nbor_list = set().union(*nbor_list)
            self.move_lists[chan_idx] = move_list
            self.nbor_lists[chan_idx] = nbor_list

        logging.info('DPA Result movelist `%s`- MOVE_LIST:%s NBOR_LIST: %s',
                     self.name, self.move_lists, self.nbor_lists)
Example #5
0
def PpaCreationModel(devices, pal_records):
    """Creates a PPA Polygon based on the PAL Records and Device Information
  Args:
    devices: A list of CBSD records (schema |CbsdRecordData|).
    pal_records: A list of pal records.
  Returns:
    The PPA polygon in GeoJSON format (string).
  """
    # Validation for Inputs
    for device in devices:
        logging.info('Validating device', device)
        util.assertContainsRequiredFields("RegistrationRequest.schema.json",
                                          device)
    for pal_rec in pal_records:
        logging.info('Validating pal_rec', pal_rec)
        util.assertContainsRequiredFields("PalRecord.schema.json", pal_rec)

    # Create Contour for each CBSD
    pool = mpool.Pool()
    device_polygon = pool.map(_GetPolygon, devices)

    # Create Union of all the CBSD Contours and Check for hole
    # after Census Tract Clipping
    contour_union = ops.cascaded_union(device_polygon)
    logging.info('contour_union = %s', contour_union)

    ppa_without_small_holes = utils.PolyWithoutSmallHoles(contour_union)
    logging.info('ppa_without_small_holes = %s', ppa_without_small_holes)

    ppa_polygon = _ClipPpaByCensusTract(ppa_without_small_holes, pal_records)
    logging.info('contour_union clipped by census tracts: %s', ppa_polygon)
    if ppa_polygon.is_empty:
        raise Exception("Empty Polygon is generated, please check the inputs.")
    if ppa_polygon.geom_type == "MultiPolygon":
        raise Exception(
            "Multi Polygon is not supported, please check the inputs.")

    # Convert Shapely Object to GeoJSON geometry string
    return utils.ToGeoJson(ppa_polygon)
def CheckTerrainTileCacheOk():
    """Check tiles cache well behaved."""
    print('Check of tile cache swap:')
    num_workers = mpool.GetNumWorkerProcesses()
    if num_workers:
        tile_stats = mpool.RunOnEachWorkerProcess(_getTileStats)
    else:
        tile_stats = [_getTileStats()]
    num_active_tiles, cnt_per_tile = tile_stats[np.argmax(
        [tile_stat[0] for tile_stat in tile_stats])]
    if not num_active_tiles:
        print('-- Cache ERROR: No active tiles read')
    elif max(cnt_per_tile) > 1:
        print(
            '-- Cache WARNING: cache tile too small - tiles are swapping from cache.'
        )
        if num_workers:
            pool = mpool.Pool()
            pool.apply_async(_printTileStats)
        else:
            _printTileStats()
    else:
        print('-- Cache tile: OK (no swapping)')
def findMoveList(protection_specs,
                 protection_points,
                 registration_requests,
                 grant_requests,
                 num_iter,
                 num_processes,
                 pool=None):
    """Main routine to find CBSD indices on the move list.

  Inputs:
    protection_specs:   protection specifications, an object with attributes
                        'lowFreq' (in Hz), 'highFreq' (in Hz),
                        'antHeight' (in meters), 'beamwidth' (in degrees),
                        'threshold' (in dBm/10MHz), 'neighbor_distances' (km),
                        'min_azimuth', 'max_azimuth' (degrees)
                        where `neighbor_distances` are the neighborhood
                        distances (km) as a sequence:
                          [cata_dist, catb_dist, cata_oob_dist, catb_oob_dist]
    protection_points:  a list of protection points, each one being an object
                        providing attributes 'latitude' and 'longitude'
    registration_requests: a list of CBSD registration requests, each one being
                           a dictionary containing CBSD registration information
    grant_requests:     a list of grant requests, each one being a dictionary
                        containing grant information; there is a one-to-one
                        mapping between items in registration_requests and
                        grant_requests; a CBSD with more than one grant will
                        have corresponding duplicate items in registration_requests
    num_iter:           number of Monte Carlo iterations
    num_processes:      number of parallel processes to use
    pool:               optional |multiprocessing.Pool| to use

  Returns:
    result:             a Boolean list (same size as registration_requests/
                        grant_requests) with TRUE elements at indices having
                        grants on the move list
  """
    grants = data.getGrantsFromRequests(registration_requests, grant_requests)
    # Find the move list of each protection constraint with a pool of parallel processes.
    if pool is None:
        mpool.Configure(num_processes)
        pool = mpool.Pool()

    neighbor_distances = protection_specs.neighbor_distances
    try:
        min_azimuth = protection_specs.min_azimuth
        max_azimuth = protection_specs.max_azimuth
    except AttributeError:
        min_azimuth, max_azimuth = 0, 360

    moveListC = partial(moveListConstraint,
                        low_freq=protection_specs.lowFreq,
                        high_freq=protection_specs.highFreq,
                        grants=grants,
                        inc_ant_height=protection_specs.antHeight,
                        num_iter=num_iter,
                        threshold=protection_specs.threshold,
                        beamwidth=protection_specs.beamwidth,
                        min_azimuth=min_azimuth,
                        max_azimuth=max_azimuth,
                        neighbor_distances=neighbor_distances)
    M_c, _ = zip(*pool.map(moveListC, protection_points))

    # Find the unique CBSD indices in the M_c list of lists.
    M = set().union(*M_c)

    # Set to TRUE the elements of the output Boolean array that have grant_index in
    # the move list.
    result = np.zeros(len(grants), bool)
    for k, grant in enumerate(grants):
        if grant in M:
            result[k] = True
    output = result.tolist()
    return output
Example #8
0
    def CheckInterference(self,
                          sas_uut_active_grants,
                          margin_db,
                          channel=None,
                          num_iter=None,
                          do_abs_check_single_uut=False,
                          extensive_print=True,
                          output_data=None):
        """Checks interference of keep list of SAS UUT vs test harness.

    This compares the aggregated interference (within some margin) generated by:
      - the test harness keep list, versus
      - the blended SAS UUT / Test harness keep list
    for all protection points and azimuth.

    Args:
      sas_uut_active_grants: An iterable of authorized |data.CbsdGrantInfo| grants.
        of the SAS UUT (ie not in the move list).
      margin_db: Defines the method and margin (in dB) to use for checking the SAS UUT
        aggregate interference:
        - a number: the check is done against the reference model aggregate interference
          using this value as the margin (in dB). [Legacy method].
        - a string of one of the following type:
          + 'target(<number_db>)': the check is done against the DPA protection threshold,
             with <number> being an allowed margin (in dB).
          + 'linear(<number_db>): the check is done against the ref model, but by using a
            margin in linear domain. The `number_db` is still given in dB, but converted
            into an equivalent mW margin for the target threshold. Then this mW margin is
            used for the check.
      channel: A channel as tuple (low_freq_mhz, high_freq_mhz), or None for all channels
        of that Dpa.
      num_iter: The number of Monte Carlo iteration for calculating the aggregate
        interference. If None, use the global class level `num_iteration`.
      do_abs_check_single_uut: If True, performs check against absolute threshold instead
        of relative check against ref model, iff only SAS UUT present (no peer SAS).
        In this mode, move list does not need to be precomputed, or grants setup,
        (only the passed UUT active grants are used).
      extensive_print: If True, extensive logging done onto local files.
      output_result: If an empty list, then it will be populated with detailed
        interference results (|DpaInterferenceResult|) for each protected point
        and channel. Indexing is [chan_idx, point_idx], unless channel is unique,
        in which case indexing is [point_idx].

    Returns:
      True if all SAS UUT aggregated interference are within margin, False otherwise
      (ie if at least one combined protection point / azimuth fails the test).
    """
        if channel is None:
            test_passed = True
            if output_data == []:
                output_data.extend([[]] * len(self._channels))
            else:
                output_data = [None] * len(self._channels)
            for k, chan in enumerate(self._channels):
                if not self.CheckInterference(
                        sas_uut_active_grants, margin_db, chan, num_iter,
                        do_abs_check_single_uut, extensive_print,
                        output_data[k]):
                    test_passed = False
            return test_passed

        if num_iter is None:
            num_iter = Dpa.num_iteration

        # Manages the various margin_db methods.
        margin_method = 'std'
        if isinstance(margin_db, basestring):
            idx1, idx2 = margin_db.find('('), margin_db.find(')')
            if idx1 == -1 or idx2 == -1:
                raise ValueError(
                    'DPA CheckInterference: margin_db: `%s` not allowed.'
                    'Use a number,  the `target(xx)` or `linear(xx)` options' %
                    margin_db)
            margin_method = margin_db[:idx1].strip().lower()
            if margin_method not in ['target', 'linear']:
                raise ValueError(
                    'DPA CheckInterference: margin_db method: `%s` not allowed.'
                    'Use either `target(xx)` or `linear(xx)` options' %
                    margin_method)
            margin_db = float(margin_db[idx1 + 1:idx2].strip())

        # Find the keep list component of TH: SAS UUT and peer SASes.
        keep_list = self.GetKeepList(channel)
        keep_list_th_other_sas = [
            grant for grant in self._grants
            if (not grant.is_managed_grant and grant in keep_list)
        ]
        keep_list_th_managing_sas = [
            grant for grant in self._grants
            if (grant.is_managed_grant and grant in keep_list)
        ]

        # Makes sure we have a list of SAS UUT active grants
        sas_uut_active_grants = list(sas_uut_active_grants)

        if extensive_print:
            # Derive the estimated SAS UUT keep list, ie the SAS UUT active grants
            # within the neighborhood (defined by distance and frequency). This is
            # only insured to be a superset of the actual keep list.
            # Note: to avoid any test harness regression, this list is currently only
            # used for logging purpose (although it could be passed to the interference
            # check routine for faster operation).
            est_keep_list_uut_managing_sas = ml.getDpaNeighborGrants(
                sas_uut_active_grants,
                self.protected_points,
                low_freq=channel[0] * 1e6,
                high_freq=channel[1] * 1e6,
                neighbor_distances=self.neighbor_distances)
            try:
                self.__PrintKeepLists(keep_list_th_other_sas,
                                      keep_list_th_managing_sas,
                                      est_keep_list_uut_managing_sas,
                                      self.name, channel)
            except Exception as e:
                logging.error('Could not print DPA keep lists: %s', e)

        # Do absolute threshold in some case
        hard_threshold = None
        if margin_method == 'target' or (do_abs_check_single_uut
                                         and not self._has_th_grants):
            hard_threshold = self.threshold

        logging.info(
            'DPA Check interf `%s`- channel %s thresh %s bw %s '
            'iter %s azi_range %s nbor_dists %s', self.name, channel,
            hard_threshold if hard_threshold else
            ('`MoveList`' if margin_method == 'std' else 'MoveList + Linear'),
            self.beamwidth, num_iter, self.azimuth_range,
            self.neighbor_distances)

        checkPointInterf = functools.partial(
            _CalcTestPointInterfDiff,
            channel=channel,
            keep_list_th_other_sas=keep_list_th_other_sas,
            keep_list_th_managing_sas=keep_list_th_managing_sas,
            keep_list_uut_managing_sas=sas_uut_active_grants,
            radar_height=self.radar_height,
            beamwidth=self.beamwidth,
            num_iter=num_iter,
            azimuth_range=self.azimuth_range,
            neighbor_distances=self.neighbor_distances,
            threshold=hard_threshold)
        pool = mpool.Pool()
        result = pool.map(checkPointInterf, self.protected_points)

        if output_data == []:
            output_data.extend(result)

        margin_mw = None  # for standard or target method
        if margin_method == 'linear':  # linear method
            margin_mw = Db2Lin(self.threshold + margin_db) - Db2Lin(
                self.threshold)

        if extensive_print:
            try:
                self.__PrintStatistics(result, self.name, channel,
                                       self.threshold, margin_mw)
            except Exception as e:
                logging.error('Could not print DPA statistics: %s', e)

        if margin_mw is None:  # standard or target method
            max_diff_interf = max(r.max_difference for r in result)
            if max_diff_interf > margin_db:
                logging.warning(
                    'DPA Check Fail `%s`- channel %s thresh %s max_diff %s',
                    self.name, channel,
                    hard_threshold if hard_threshold else '`MoveList`',
                    max_diff_interf)
            else:
                logging.info('DPA Check Succeed - max_diff %s',
                             max_diff_interf)
            return max_diff_interf <= margin_db

        else:  # Linear method
            max_diff_interf_mw = max([
                np.max(Db2Lin(r.A_DPA) - Db2Lin(r.A_DPA_ref)) for r in result
            ])
            if max_diff_interf_mw > margin_mw:
                logging.warning(
                    'DPA Check Fail `%s`- channel %s thresh `MoveList+Linear`'
                    ' margin_mw excess by %.4fdB', self.name, channel,
                    Lin2Db(max_diff_interf_mw / margin_mw))
            else:
                logging.info(
                    'DPA Check Succeed - margin_mw headroom by %.4fdB',
                    Lin2Db(max_diff_interf_mw / margin_mw))

            return max_diff_interf_mw <= margin_mw
Example #9
0
def performIapForPpa(ppa_record, sas_uut_fad_object, sas_th_fad_objects,
                     pal_records):
    """Computes post IAP interference margin for PPA incumbents.

  Routine to get protection points within PPA protection area and perform
  IAP algorithm on each protection point.

  Args:
    ppa_record: A PPA record dict.
    sas_uut_fad_object: A FAD object from SAS UUT.
    sas_th_fad_object: A list of FAD objects from SAS Test Harness
    pal_records: PAL records associated with a PPA protection area
  Returns:
    ap_iap_ref: The post-IAP allowed interference, as a dict formatted as:
        {latitude : {longitude : [interference(mW/IAPBW), .., interference(mW/IAPBW)]}}
      where the list holds all values per channel of that protection point.
  """
    ppa_thresh_q = THRESH_PPA_DBM_PER_IAPBW

    # Actual protection threshold used for IAP - calculated by applying
    # a pre-defined Pre-IAP headroom (Mg) at each protection threshold(Q)
    ppa_iap_threshold = interf.dbToLinear(ppa_thresh_q - MARGIN_PPA_DB)

    grants = data.getGrantObjectsFromFAD(sas_uut_fad_object,
                                         sas_th_fad_objects, ppa_record)

    # Get number of SAS
    num_sas = len(sas_th_fad_objects) + 1

    logging.debug('$$$$ Getting GRID points for PPA Protection Area $$$$')

    # Get Fine Grid Points for a PPA protection area
    protection_points = utils.GridPolygon(
        ppa_record['zone']['features'][0]['geometry'], PPA_GRID_RES_ARCSEC)

    # Get the region type of the PPA protection area
    ppa_region = ppa_record['ppaInfo']['ppaRegionType']

    # Find the PAL records for the PAL_IDs defined in PPA records
    ppa_pal_ids = ppa_record['ppaInfo']['palId']

    matching_pal_records = [
        pr for pr in pal_records if pr['palId'] == ppa_pal_ids[0]
    ]
    if not matching_pal_records:
        raise ValueError('No matching PAL record, please check input')

    pal_record = matching_pal_records[0]

    # Get the frequencies from the PAL records
    ppa_freq_range = pal_record['channelAssignment']['primaryAssignment']
    ppa_low_freq = ppa_freq_range['lowFrequency']
    ppa_high_freq = ppa_freq_range['highFrequency']

    # Get channels over which area incumbent needs partial/full protection
    protection_channels = interf.getProtectedChannels(ppa_low_freq,
                                                      ppa_high_freq)

    # Apply IAP for each protection constraint with a pool of parallel
    # processes.
    logging.debug('$$$$ Calling PPA Protection $$$$')
    iapPoint = partial(iapPointConstraint,
                       channels=protection_channels,
                       low_freq=ppa_low_freq,
                       high_freq=ppa_high_freq,
                       grants=grants,
                       fss_info=None,
                       esc_antenna_info=None,
                       region_type=ppa_region,
                       threshold=ppa_iap_threshold,
                       protection_ent_type=data.ProtectedEntityType.PPA_AREA)

    pool = mpool.Pool()
    iap_interfs = pool.map(iapPoint, protection_points)

    ap_iap_ref = calculatePostIapAggregateInterference(
        interf.dbToLinear(ppa_thresh_q), num_sas, iap_interfs)

    return ap_iap_ref
Example #10
0
def performIapForGwpz(gwpz_record, sas_uut_fad_object, sas_th_fad_objects):
    """Computes post IAP interference margin for GWPZ incumbents.

  Routine to get protection points within GWPZ protection area and perform
  IAP algorithm on each protection point.

  Args:
    gwpz_record: A GWPZ record dict.
    sas_uut_fad_object: FAD object from SAS UUT
    sas_th_fad_objects: A list of FAD objects from SAS Test Harness
  Returns:
    ap_iap_ref: The post-IAP allowed interference, as a dict formatted as:
        {latitude : {longitude : [interference(mW/IAPBW), .., interference(mW/IAPBW)]}}
      where the list holds all values per channel of that protection point.
  """
    gwpz_thresh_q = THRESH_GWPZ_DBM_PER_IAPBW

    # Actual protection threshold used for IAP - calculated by applying
    # a pre-defined Pre-IAP headroom (Mg) at each protection threshold(Q)
    gwpz_iap_threshold = interf.dbToLinear(gwpz_thresh_q - MARGIN_GWPZ_DB)

    grants = data.getGrantObjectsFromFAD(sas_uut_fad_object,
                                         sas_th_fad_objects)

    # Get number of SAS
    num_sas = len(sas_th_fad_objects) + 1

    logging.debug('$$$$ Getting GRID points for GWPZ Protection Area $$$$')
    # Get Fine Grid Points for a GWPZ protection area
    protection_points = utils.GridPolygon(
        gwpz_record['zone']['features'][0]['geometry'], GWPZ_GRID_RES_ARCSEC)

    gwpz_freq_range = gwpz_record['record']['deploymentParam'][0]\
                                 ['operationParam']['operationFrequencyRange']
    gwpz_low_freq = gwpz_freq_range['lowFrequency']
    gwpz_high_freq = gwpz_freq_range['highFrequency']
    gwpz_region = gwpz_record['landCategory']

    # Get channels over which area incumbent needs partial/full protection
    protection_channels = interf.getProtectedChannels(gwpz_low_freq,
                                                      gwpz_high_freq)

    logging.debug('$$$$ Calling GWPZ Protection $$$$')
    iapPoint = partial(iapPointConstraint,
                       channels=protection_channels,
                       low_freq=gwpz_low_freq,
                       high_freq=gwpz_high_freq,
                       grants=grants,
                       fss_info=None,
                       esc_antenna_info=None,
                       region_type=gwpz_region,
                       threshold=gwpz_iap_threshold,
                       protection_ent_type=data.ProtectedEntityType.GWPZ_AREA)

    pool = mpool.Pool()
    iap_interfs = pool.map(iapPoint, protection_points)

    ap_iap_ref = calculatePostIapAggregateInterference(
        interf.dbToLinear(gwpz_thresh_q), num_sas, iap_interfs)

    return ap_iap_ref
    def CheckInterference(self,
                          sas_uut_active_grants,
                          margin_db,
                          channel=None,
                          num_iter=None,
                          do_abs_check_single_uut=False):
        """Checks interference of keep list of SAS UUT vs test harness.

    This compares the aggregated interference (within some margin) generated by:
      - the test harness keep list, versus
      - the blended SAS UUT / Test harness keep list
    for all protection points and azimuth.

    Args:
      sas_uut_active_grants: An iterable of authorized |data.CbsdGrantInfo| grants.
        of the SAS UUT (ie not in the move list).
      margin_db: The margin (in dB) to use for the check.
      channel: A channel as tuple (low_freq_mhz, high_freq_mhz), or None for all channels
        of that Dpa.
      num_iter: The number of Monte Carlo iteration for calculating the aggregate
        interference. If None, use the global class level `num_iteration`.
      do_abs_check_single_uut: If True, performs check against absolute threshold instead
        of relative check against ref model, iff only SAS UUT present (no peer SAS).
        In this mode, move list does not need to be precomputed, or grants setup,
        (only the passed UUT active grants are used).

    Returns:
      True if all SAS UUT aggregated interference are within margin, False otherwise
      (ie if at least one combined protection point / azimuth fails the test).
    """
        if channel is None:
            for chan in self.channels:
                if not self.CheckInterference(sas_uut_active_grants, margin_db,
                                              chan, num_iter,
                                              do_abs_check_single_uut):
                    return False
            return True

        if num_iter is None:
            num_iter = Dpa.num_iteration

        # Find the keep list component of TH: SAS UUT and peer SASes.
        keep_list = self.GetKeepList(channel)
        keep_list_th_other_sas = [
            grant for grant in self._grants
            if (not grant.is_managed_grant and grant in keep_list)
        ]
        keep_list_th_managing_sas = [
            grant for grant in self._grants
            if (grant.is_managed_grant and grant in keep_list)
        ]

        # Find an extended keep list of UUT.
        keep_list_uut_managing_sas = list(sas_uut_active_grants)

        # Note: other code with pre filtering could be:
        #nbor_list = self.GetNeighborList(channel)
        #if nbor_list:
        #  # Slight optimization by prefiltering the ones in the nbor list.
        #  keep_list_uut_managing_sas = [
        #      grant for grant in sas_uut_active_grants
        #      if (grant in nbor_list and grant not in keep_list_th_other_sas)]
        #else:
        #  keep_list_uut_managing_sas = list(sas_uut_active_grants)

        # Do absolute threshold in some case
        hard_threshold = None
        if do_abs_check_single_uut and not self._has_th_grants:
            hard_threshold = self.threshold

        logging.info(
            'DPA Check interf `%s`- channel %s thresh %s bw %s '
            'iter %s azi_range %s nbor_dists %s', self.name, channel,
            hard_threshold if hard_threshold else '`MoveList`', self.beamwidth,
            num_iter, self.azimuth_range, self.neighbor_distances)
        logging.debug(
            'DPA Check interf `%s` - KL_TH_MGR: %s KL_TH_OTHER: %s KL_UUT_MGR: %s',
            self.name, keep_list_th_managing_sas, keep_list_th_other_sas,
            keep_list_uut_managing_sas)

        checkPointInterf = functools.partial(
            _CalcTestPointInterfDiff,
            channel=channel,
            keep_list_th_other_sas=keep_list_th_other_sas,
            keep_list_th_managing_sas=keep_list_th_managing_sas,
            keep_list_uut_managing_sas=keep_list_uut_managing_sas,
            radar_height=self.radar_height,
            beamwidth=self.beamwidth,
            num_iter=num_iter,
            azimuth_range=self.azimuth_range,
            neighbor_distances=self.neighbor_distances,
            threshold=hard_threshold)
        # TODO(sbdt): could do early stop as soon as one fails, although I would expect
        # the criteria to be changed into checking 99.x% of success instead of 100%.
        pool = mpool.Pool()
        result = pool.map(checkPointInterf, self.protected_points)
        max_diff_interf = max(result)

        if max_diff_interf > margin_db:
            logging.warning(
                'DPA Check Fail `%s`- channel %s thresh %s max_diff %s',
                self.name, channel,
                hard_threshold if hard_threshold else '`MoveList`',
                max_diff_interf)
        else:
            logging.info('DPA Check Succeed - max_diff %s', max_diff_interf)

        return max_diff_interf <= margin_db
def calculateAggregateInterferenceForPpa(ppa_record, pal_records, grants):
    """Calculates per-channel aggregate interference for PPA.

  Args:
    ppa_record: A PPA record dict.
    pal_records: PAL records associated with a PPA protection area
    grants: An iterable of CBSD grants of type |data.CbsdGrantInfo|.

  Returns:
    Aggregate interference to PPA in the nested dictionary format.
      {latitude : {longitude: [aggr_interf1(mW), ..., aggr_interfK(mW)]}}
    The list contains the value per protected channel.
  """
    # Get Fine Grid Points for a PPA protection area
    protection_points = utils.GridPolygon(
        ppa_record['zone']['features'][0]['geometry'], PPA_GRID_RES_ARCSEC)

    # Get the region type of the PPA protection area
    ppa_region = ppa_record['ppaInfo']['ppaRegionType']

    # Find the PAL records for the PAL_IDs defined in PPA records
    ppa_pal_ids = ppa_record['ppaInfo']['palId']

    matching_pal_records = [
        pr for pr in pal_records if pr['palId'] == ppa_pal_ids[0]
    ]
    if not matching_pal_records:
        raise ValueError(
            'AggInterf: No matching PAL record, please check input')

    pal_record = matching_pal_records[0]

    # Get the frequencies from the PAL records
    ppa_freq_range = pal_record['channelAssignment']['primaryAssignment']
    ppa_low_freq = ppa_freq_range['lowFrequency']
    ppa_high_freq = ppa_freq_range['highFrequency']

    # Get channels over which area incumbent needs partial/full protection
    protection_channels = interf.getProtectedChannels(ppa_low_freq,
                                                      ppa_high_freq)

    logging.info(
        'Computing aggregateInterferenceForPoint for PPA (%s), channels (%s), '
        'nPoints (%d), grants (%s), region_type (%s)', ppa_record,
        protection_channels, len(protection_points), grants, ppa_region)
    logging.debug('  points: %s', protection_points)

    # Calculate aggregate interference from each protection constraint with a
    # pool of parallel processes.
    interfCalculator = partial(
        aggregateInterferenceForPoint,
        channels=protection_channels,
        grants=grants,
        fss_info=None,
        esc_antenna_info=None,
        protection_ent_type=data.ProtectedEntityType.PPA_AREA,
        region_type=ppa_region)

    pool = mpool.Pool()
    interferences = pool.map(interfCalculator, protection_points)
    return InterferenceDict(interferences)
    print 'Num Protection Points: %d' % len(dpa.protected_points)
    print 'Num CBSD: %d (A: %d %d - B %d)' % (len(grants), n_a_indoor,
                                              n_a_outdoor, n_b)
    print 'Distribution: %s' % ('uniform' if not do_inside_urban_area else
                                'urban areas only')
    print 'Move list size: %d' % len_move_list
    print 'Computation time: %.1fs' % (end_time - start_time)

    # Check tiles cache well behaved
    print ''
    tile_stats = mpool.RunOnEachWorkerProcess(getTileStats)
    num_active_tiles, cnt_per_tile = tile_stats[0]
    if not num_active_tiles:
        print '-- Cache ERROR: No active tiles read'
    elif max(cnt_per_tile) > 1:
        print '-- Cache WARNING: cache tile too small - tiles are swapping from cache.'
        pool = mpool.Pool()
        pool.apply_async(printTileStats)
    else:
        print '-- Cache tile: OK (no swapping)'

    # Plot the move list
    if len_move_list:
        result = dpa.GetMoveListMask((fmin, fmax))
        ax.scatter(
            [cbsd.longitude for k, cbsd in enumerate(all_cbsds) if result[k]],
            [cbsd.latitude for k, cbsd in enumerate(all_cbsds) if result[k]],
            color='r',
            marker='.')
    plt.show()