示例#1
0
class SkyRegion(pexConfig.Config):
    """Configuration for a proposal's sky region of interest.
    """

    selections = pexConfig.ConfigDictField(
        'A list of type selections for sky region determination.', int,
        Selection)
    combiners = pexConfig.ListField(
        'A list of logical operations [and, or] that combine the region '
        'selections. Must be one less than the number of selections. If '
        'only one region, the list is left empty.', str)
    time_ranges = pexConfig.ConfigDictField(
        'A collection of time ranges for sky region selection.',
        int,
        TimeRange,
        optional=True)
    selection_mapping = pexConfig.ConfigDictField(
        'A collection of selection mapping arrays.',
        int,
        SelectionList,
        optional=True)

    def setDefaults(self):
        """Default specification for a sky region.
        """
        self.combiners = []
示例#2
0
class ReferenceSourceSelectorConfig(pexConfig.Config):
    doMagLimit = pexConfig.Field(dtype=bool,
                                 default=False,
                                 doc="Apply magnitude limit?")
    doFlags = pexConfig.Field(dtype=bool,
                              default=False,
                              doc="Apply flag limitation?")
    doUnresolved = pexConfig.Field(dtype=bool,
                                   default=False,
                                   doc="Apply unresolved limitation?")
    doSignalToNoise = pexConfig.Field(dtype=bool,
                                      default=False,
                                      doc="Apply signal-to-noise limit?")
    doMagError = pexConfig.Field(dtype=bool,
                                 default=False,
                                 doc="Apply magnitude error limit?")
    magLimit = pexConfig.ConfigField(dtype=MagnitudeLimit,
                                     doc="Magnitude limit to apply")
    flags = pexConfig.ConfigField(dtype=RequireFlags, doc="Flags to require")
    unresolved = pexConfig.ConfigField(dtype=RequireUnresolved,
                                       doc="Star/galaxy separation to apply")
    signalToNoise = pexConfig.ConfigField(dtype=SignalToNoiseLimit,
                                          doc="Signal-to-noise limit to apply")
    magError = pexConfig.ConfigField(dtype=MagnitudeErrorLimit,
                                     doc="Magnitude error limit to apply")
    colorLimits = pexConfig.ConfigDictField(
        keytype=str,
        itemtype=ColorLimit,
        default={},
        doc="Color limits to apply; key is used as a label only")
示例#3
0
class TransformMapConfig(pexConfig.Config):
    transforms = pexConfig.ConfigDictField(
        doc="Dict of coordinate system name: TransformConfig",
        keytype=str,
        itemtype=TransformConfig,
    )
    nativeSys = pexConfig.Field(
        doc="Name of native coordinate system",
        dtype=str,
        optional=False,
    )
class MasterSubSequence(BaseSequence):
    """Configuration for master sub-sequences.
    """

    name = pexConfig.Field('The identifier for the master sub-sequence.', str)
    sub_sequences = pexConfig.ConfigDictField('The set of nested sub-sequences for this master sub-sequence.',
                                              int, SubSequence)

    def setDefaults(self):
        """Default specification for MasterSubSequence information.
        """
        BaseSequence.setDefaults(self)
示例#5
0
class CameraConfig(pexConfig.Config):
    """!A configuration that represents (and can be used to construct) a Camera
    """
    detectorList = pexConfig.ConfigDictField(
        "List of detector configs", keytype=int, itemtype=DetectorConfig)
    transformDict = pexConfig.ConfigField(
        "Dictionary of camera transforms keyed on the transform type.", TransformMapConfig)
    name = pexConfig.Field("Name of this camera", str)

    plateScale = pexConfig.Field(
        "Plate scale of the camera in arcsec/mm", float)
    # Note that the radial transform will also apply a scaling, so all coefficients should be
    # scaled by the plate scale in appropriate units
    radialCoeffs = pexConfig.ListField(
        "Coefficients for radial distortion", float)
示例#6
0
class SkyExclusion(pexConfig.Config):
    """Configuration for a proposal's sky exclusions.
    """

    dec_window = pexConfig.Field(
        'Angle (units=degrees) around the observing site\'s latitude for which to '
        'create a Declination window for field selection.', float)

    selections = pexConfig.ConfigDictField(
        'A list of type selections for sky exclusion '
        'determination. Currently, only GP is supported.', int, Selection)

    def setDefaults(self):
        """Default specification for a sky exclusions.
        """
        self.dec_window = 90.0
        self.selections = {}
示例#7
0
class General(pexConfig.Config):
    """Configuration for a general proposal. This includes area distribution, time-domain
       and hybrid proposals.
    """

    name = pexConfig.Field('Name for the proposal.', str)
    sky_region = pexConfig.ConfigField(
        'Sky region selection for the proposal.', SkyRegion)
    sky_exclusion = pexConfig.ConfigField(
        'Sky region selection for the proposal.', SkyExclusion)
    sky_nightly_bounds = pexConfig.ConfigField(
        'Sky region selection for the proposal.', SkyNightlyBounds)
    sky_constraints = pexConfig.ConfigField(
        'Sky region selection for the proposal.', SkyConstraints)
    filters = pexConfig.ConfigDictField(
        'Filter configuration for the proposal.', str, GeneralBandFilter)
    scheduling = pexConfig.ConfigField(
        'Scheduling configuration for the proposal.', GeneralScheduling)

    def proposal_fields(self, fd, fs):
        """Return the field Ids for this proposal.

        Parameters
        ----------
        fd : lsst.sims.survey.fields.FieldsDatabase
            An instance of the fields database.
        fs : lsst.sims.survey.fields.FieldSelection
            An instance of the field selector.

        Returns
        -------
        list[int]
        """
        query_list = []
        combine_list = []
        region_cuts = []

        # Handle any time dependent cuts
        try:
            num_selections = len(self.sky_region.selection_mapping)
            for i, mapping in self.sky_region.selection_mapping.items():
                for index in mapping.indexes:
                    region_cuts.append(self.sky_region.selections[index])
                try:
                    combine_list.append(self.sky_region.combiners[i])
                except IndexError:
                    # Don't have combiners, must be single selection per time range
                    pass
                if i < num_selections - 1:
                    combine_list.append("or")
        except TypeError:
            region_cuts = list(self.sky_region.selections.values())
            combine_list.extend(self.sky_region.combiners)

        # Handle the sky region selections
        for cut in region_cuts:
            cut_type = cut.limit_type
            if cut_type != "GP":
                query_list.append(
                    fs.select_region(cut_type, cut.minimum_limit,
                                     cut.maximum_limit))
            else:
                query_list.append(
                    fs.galactic_region(cut.maximum_limit, cut.minimum_limit,
                                       cut.bounds_limit))

        # Handle the sky exclusion selections
        exclusion_query = None
        for cut in self.sky_exclusion.selections.values():
            cut_type = cut.limit_type
            if cut_type == "GP":
                # Need the field Ids, so don't mark it as an exclusion
                exclusion_query = fs.galactic_region(cut.maximum_limit,
                                                     cut.minimum_limit,
                                                     cut.bounds_limit)

        query = fs.combine_queries(*query_list, combiners=combine_list)
        fields = fd.get_field_set(query)
        ids = set([x[0] for x in fields])
        if exclusion_query is not None:
            equery = fs.combine_queries(exclusion_query)
            efields = fd.get_field_set(equery)
            eids = set([x[0] for x in efields])
            ids.difference_update(eids)

        return sorted(list(ids))

    def set_topic(self, topic):
        """Set the information on a DDS topic instance.

        Parameters
        ----------
        topic : SALPY_scheduler.scheduler_generalPropConfigC
            The instance of the DDS topic to set information on.

        Returns
        -------
        SALPY_scheduler.scheduler_generalPropConfigC
            The topic with current information set.
        """
        topic.name = self.name if self.name is not None else "None"

        topic.twilightBoundary = self.sky_nightly_bounds.twilight_boundary
        topic.deltaLst = self.sky_nightly_bounds.delta_lst
        topic.decWindow = self.sky_exclusion.dec_window
        topic.maxAirmass = self.sky_constraints.max_airmass
        topic.maxCloud = self.sky_constraints.max_cloud
        topic.minDistanceMoon = self.sky_constraints.min_distance_moon
        topic.excludePlanets = self.sky_constraints.exclude_planets

        num_region_selections = len(self.sky_region.selections) \
            if self.sky_region.selections is not None else 0
        topic.numRegionSelections = num_region_selections
        if num_region_selections:
            limit_types = []
            for i, v in enumerate(self.sky_region.selections.values()):
                limit_types.append(v.limit_type)
                topic.regionMinimums[i] = v.minimum_limit
                topic.regionMaximums[i] = v.maximum_limit
                topic.regionBounds[i] = v.bounds_limit
            topic.regionTypes = ','.join(limit_types)

        topic.regionCombiners = ','.join(self.sky_region.combiners)

        num_time_ranges = len(
            self.sky_region.time_ranges
        ) if self.sky_region.time_ranges is not None else 0
        topic.numTimeRanges = num_time_ranges
        if num_time_ranges:
            for i, v in enumerate(self.sky_region.time_ranges.values()):
                topic.timeRangeStarts[i] = v.start
                topic.timeRangeEnds[i] = v.end

        num_selection_mappings = len(self.sky_region.selection_mapping) \
            if self.sky_region.selection_mapping is not None else 0
        if num_selection_mappings:
            selection_index = 0
            for i, v in enumerate(self.sky_region.selection_mapping.values()):
                topic.numSelectionMappings[i] = len(v.indexes)
                for index in v.indexes:
                    topic.selectionMappings[selection_index] = index
                    selection_index += 1

        num_exclusion_selections = len(self.sky_exclusion.selections) \
            if self.sky_exclusion.selections is not None else 0
        topic.numExclusionSelections = num_exclusion_selections
        if num_exclusion_selections:
            limit_types = []
            for i, v in enumerate(self.sky_exclusion.selections.values()):
                limit_types.append(v.limit_type)
                topic.exclusionMinimums[i] = v.minimum_limit
                topic.exclusionMaximums[i] = v.maximum_limit
                topic.exclusionBounds[i] = v.bounds_limit
            topic.exclusionTypes = ','.join(limit_types)

        topic.numFilters = len(self.filters) if self.filters is not None else 0
        if topic.numFilters:
            filter_names = []
            exp_index = 0
            for i, v in enumerate(self.filters.values()):
                filter_names.append(v.name)
                topic.numVisits[i] = v.num_visits
                topic.numGroupedVisits[i] = v.num_grouped_visits
                topic.maxGroupedVisits[i] = v.max_grouped_visits
                topic.brightLimit[i] = v.bright_limit
                topic.darkLimit[i] = v.dark_limit
                topic.maxSeeing[i] = v.max_seeing
                topic.numFilterExposures[i] = len(v.exposures)
                for exposure in v.exposures:
                    topic.exposures[exp_index] = exposure
                    exp_index += 1
            topic.filterNames = ','.join(filter_names)

        topic.maxNumTargets = self.scheduling.max_num_targets
        topic.acceptSerendipity = self.scheduling.accept_serendipity
        topic.acceptConsecutiveVisits = self.scheduling.accept_consecutive_visits
        topic.airmassBonus = self.scheduling.airmass_bonus
        topic.hourAngleBonus = self.scheduling.hour_angle_bonus
        topic.hourAngleMax = self.scheduling.hour_angle_max
        topic.restrictGroupedVisits = self.scheduling.restrict_grouped_visits
        topic.timeInterval = self.scheduling.time_interval
        topic.timeWindowStart = self.scheduling.time_window_start
        topic.timeWindowMax = self.scheduling.time_window_max
        topic.timeWindowEnd = self.scheduling.time_window_end
        topic.timeWeight = self.scheduling.time_weight
        topic.fieldRevisitLimit = self.scheduling.field_revisit_limit

        return topic
class DiffMatchedTractCatalogConfig(
    pipeBase.PipelineTaskConfig,
    pipelineConnections=DiffMatchedTractCatalogConnections,
):
    column_matched_prefix_ref = pexConfig.Field(
        dtype=str,
        default='refcat_',
        doc='The prefix for matched columns copied from the reference catalog',
    )
    column_ref_extended = pexConfig.Field(
        dtype=str,
        default='is_pointsource',
        doc='The boolean reference table column specifying if the target is extended',
    )
    column_ref_extended_inverted = pexConfig.Field(
        dtype=bool,
        default=True,
        doc='Whether column_ref_extended specifies if the object is compact, not extended',
    )
    column_target_extended = pexConfig.Field(
        dtype=str,
        default='refExtendedness',
        doc='The target table column estimating the extendedness of the object (0 <= x <= 1)',
    )

    @property
    def columns_in_ref(self) -> Set[str]:
        columns_all = [self.coord_format.column_ref_coord1, self.coord_format.column_ref_coord2,
                       self.column_ref_extended]
        for column_lists in (
            (
                self.columns_ref_copy,
            ),
            (x.columns_in_ref for x in self.columns_flux.values()),
        ):
            for column_list in column_lists:
                columns_all.extend(column_list)

        return set(columns_all)

    @property
    def columns_in_target(self) -> Set[str]:
        columns_all = [self.coord_format.column_target_coord1, self.coord_format.column_target_coord2,
                       self.column_target_extended]
        if self.coord_format.coords_ref_to_convert is not None:
            columns_all.extend(self.coord_format.coords_ref_to_convert.values())
        for column_lists in (
            (
                self.columns_target_coord_err,
                self.columns_target_select_false,
                self.columns_target_select_true,
                self.columns_target_copy,
            ),
            (x.columns_in_target for x in self.columns_flux.values()),
        ):
            for column_list in column_lists:
                columns_all.extend(column_list)
        return set(columns_all)

    columns_flux = pexConfig.ConfigDictField(
        keytype=str,
        itemtype=MatchedCatalogFluxesConfig,
        doc="Configs for flux columns for each band",
    )
    columns_ref_copy = pexConfig.ListField(
        dtype=str,
        default=set(),
        doc='Reference table columns to copy to copy into cat_matched',
    )
    columns_target_coord_err = pexConfig.ListField(
        dtype=str,
        listCheck=lambda x: (len(x) == 2) and (x[0] != x[1]),
        doc='Target table coordinate columns with standard errors (sigma)',
    )
    columns_target_copy = pexConfig.ListField(
        dtype=str,
        default=('patch',),
        doc='Target table columns to copy to copy into cat_matched',
    )
    columns_target_select_true = pexConfig.ListField(
        dtype=str,
        default=('detect_isPrimary',),
        doc='Target table columns to require to be True for selecting sources',
    )
    columns_target_select_false = pexConfig.ListField(
        dtype=str,
        default=('merge_peak_sky',),
        doc='Target table columns to require to be False for selecting sources',
    )
    coord_format = pexConfig.ConfigField(
        dtype=ConvertCatalogCoordinatesConfig,
        doc="Configuration for coordinate conversion",
    )
    extendedness_cut = pexConfig.Field(
        dtype=float,
        default=0.5,
        doc='Minimum extendedness for a measured source to be considered extended',
    )
    mag_num_bins = pexConfig.Field(
        doc='Number of magnitude bins',
        default=15,
        dtype=int,
    )
    mag_brightest_ref = pexConfig.Field(
        dtype=float,
        default=15,
        doc='Brightest magnitude cutoff for binning',
    )
    mag_ceiling_target = pexConfig.Field(
        dtype=float,
        default=None,
        optional=True,
        doc='Ceiling (maximum/faint) magnitude for target sources',
    )
    mag_faintest_ref = pexConfig.Field(
        dtype=float,
        default=30,
        doc='Faintest magnitude cutoff for binning',
    )
    mag_zeropoint_ref = pexConfig.Field(
        dtype=float,
        default=31.4,
        doc='Magnitude zeropoint for reference sources',
    )
    mag_zeropoint_target = pexConfig.Field(
        dtype=float,
        default=31.4,
        doc='Magnitude zeropoint for target sources',
    )
    percentiles = pexConfig.ListField(
        dtype=str,
        # -2, -1, +1, +2 sigma percentiles for normal distribution
        default=('2.275', '15.866', '84.134', '97.725'),
        doc='Percentiles to compute for diff/chi values',
        itemCheck=is_percentile,
        listCheck=is_sequence_set,
    )
示例#9
0
 class BadItemCheck(pexConfig.Config):
     d = pexConfig.ConfigDictField("...", keytype=str, itemtype=Config1, itemCheck=4)
示例#10
0
 class BadItemtype(pexConfig.Config):
     d = pexConfig.ConfigDictField("...", keytype=int, itemtype=dict)
示例#11
0
 class BadKeytype(pexConfig.Config):
     d = pexConfig.ConfigDictField("...", keytype=list, itemtype=Config1)
示例#12
0
class Config3(pexConfig.Config):
    field1 = pexConfig.ConfigDictField(keytype=str, itemtype=pexConfig.Config, default={}, doc='doc')
示例#13
0
class Config2(pexConfig.Config):
    d1 = pexConfig.ConfigDictField("d1", keytype=str, itemtype=Config1, itemCheck=lambda x: x.f > 0)
示例#14
0
class Sequence(pexConfig.Config):
    """Configuration for a sequence proposal. This includes sequence, sub-sequence and
       nested sub-sequence proposals.
    """

    name = pexConfig.Field('Name for the proposal.', str)
    sky_user_regions = pexConfig.ListField(
        'Sky user regions for the proposal as a list of field Ids.', int)
    sky_exclusion = pexConfig.ConfigField(
        'Sky region selection for the proposal.', SkyExclusion)
    sky_nightly_bounds = pexConfig.ConfigField(
        'Sky region selection for the proposal.', SkyNightlyBounds)
    sky_constraints = pexConfig.ConfigField(
        'Sky region selection for the proposal.', SkyConstraints)
    sub_sequences = pexConfig.ConfigDictField('Set of sub-sequences.', int,
                                              SubSequence)
    master_sub_sequences = pexConfig.ConfigDictField(
        'Set of master sub-sequences.', int, MasterSubSequence)
    filters = pexConfig.ConfigDictField(
        'Filter configuration for the proposal.', str, BandFilter)
    scheduling = pexConfig.ConfigField(
        'Scheduling configuration for the proposal.', SequenceScheduling)

    def setDefaults(self):
        """Default specification for a sequence proposal.
        """
        self.sky_user_regions = []
        self.sub_sequences = {}
        self.master_sub_sequences = {}

    def proposal_fields(self):
        """Return the list of field Ids for this proposal.

        Returns
        -------
        list[int]
        """
        return sorted(self.sky_user_regions)

    def set_topic(self, topic):
        """Set the information on a DDS topic instance.

        Parameters
        ----------
        topic : SALPY_scheduler.scheduler_sequencePropConfigC
            The instance of the DDS topic to set information on.

        Returns
        -------
        SALPY_scheduler.scheduler_sequencePropConfigC
            The topic with current information set.
        """
        topic.name = self.name if self.name is not None else "None"

        topic.twilight_boundary = self.sky_nightly_bounds.twilight_boundary
        topic.delta_lst = self.sky_nightly_bounds.delta_lst
        topic.dec_window = self.sky_exclusion.dec_window
        topic.max_airmass = self.sky_constraints.max_airmass
        topic.max_cloud = self.sky_constraints.max_cloud
        topic.min_distance_moon = self.sky_constraints.min_distance_moon
        topic.exclude_planets = self.sky_constraints.exclude_planets

        num_sky_user_regions = len(self.sky_user_regions)
        topic.num_user_regions = num_sky_user_regions
        for i, sky_user_region in enumerate(self.sky_user_regions):
            topic.user_region_ids[i] = sky_user_region

        num_sub_sequences = len(
            self.sub_sequences) if self.sub_sequences is not None else 0
        topic.num_sub_sequences = num_sub_sequences
        if topic.num_sub_sequences:
            sub_sequence_names = []
            sub_sequence_filters = []
            filter_visit_index = 0
            for i, sub_sequence in self.sub_sequences.items():
                sub_sequence_names.append(sub_sequence.name)
                sub_sequence_filters.append(sub_sequence.get_filter_string())
                topic.num_sub_sequence_filters[i] = len(sub_sequence.filters)
                for filter_visit in sub_sequence.visits_per_filter:
                    topic.num_sub_sequence_filter_visits[
                        filter_visit_index] = filter_visit
                    filter_visit_index += 1
                topic.num_sub_sequence_events[i] = sub_sequence.num_events
                topic.num_sub_sequence_max_missed[
                    i] = sub_sequence.num_max_missed
                topic.sub_sequence_time_intervals[
                    i] = sub_sequence.time_interval
                topic.sub_sequence_time_window_starts[
                    i] = sub_sequence.time_window_start
                topic.sub_sequence_time_window_maximums[
                    i] = sub_sequence.time_window_max
                topic.sub_sequence_time_window_ends[
                    i] = sub_sequence.time_window_end
                topic.sub_sequence_time_weights[i] = sub_sequence.time_weight

            topic.sub_sequence_names = ",".join(sub_sequence_names)
            topic.sub_sequence_filters = ",".join(sub_sequence_filters)

        num_master_sub_sequences = len(self.master_sub_sequences) \
            if self.master_sub_sequences is not None else 0
        topic.num_master_sub_sequences = num_master_sub_sequences
        if topic.num_master_sub_sequences:
            master_sub_sequence_names = []
            nested_sub_sequence_names = []
            nested_sub_sequence_filters = []
            nss_index = 0
            filter_visit_index = 0
            for i, master_sub_sequence in self.master_sub_sequences.items():
                master_sub_sequence_names.append(master_sub_sequence.name)
                topic.num_nested_sub_sequences[i] = len(
                    master_sub_sequence.sub_sequences)
                topic.num_master_sub_sequence_events[
                    i] = master_sub_sequence.num_events
                topic.num_master_sub_sequence_max_missed[
                    i] = master_sub_sequence.num_max_missed
                topic.master_sub_sequence_time_intervals[
                    i] = master_sub_sequence.time_interval
                topic.master_sub_sequence_time_window_starts[
                    i] = master_sub_sequence.time_window_start
                topic.master_sub_sequence_time_window_maximums[
                    i] = master_sub_sequence.time_window_max
                topic.master_sub_sequence_time_window_ends[
                    i] = master_sub_sequence.time_window_end
                topic.master_sub_sequence_time_weights[
                    i] = master_sub_sequence.time_weight
                for sub_sequence in master_sub_sequence.sub_sequences.values():
                    nested_sub_sequence_names.append(sub_sequence.name)
                    nested_sub_sequence_filters.append(
                        sub_sequence.get_filter_string())
                    topic.num_nested_sub_sequence_filters[nss_index] = len(
                        sub_sequence.filters)
                    for filter_visit in sub_sequence.visits_per_filter:
                        topic.num_nested_sub_sequence_filter_visits[
                            filter_visit_index] = filter_visit
                        filter_visit_index += 1
                    topic.num_nested_sub_sequence_events[
                        nss_index] = sub_sequence.num_events
                    topic.num_nested_sub_sequence_max_missed[
                        nss_index] = sub_sequence.num_max_missed
                    topic.nested_sub_sequence_time_intervals[
                        nss_index] = sub_sequence.time_interval
                    topic.nested_sub_sequence_time_window_starts[
                        nss_index] = sub_sequence.time_window_start
                    topic.nested_sub_sequence_time_window_maximums[
                        nss_index] = sub_sequence.time_window_max
                    topic.nested_sub_sequence_time_window_ends[
                        nss_index] = sub_sequence.time_window_end
                    topic.nested_sub_sequence_time_weights[
                        nss_index] = sub_sequence.time_weight
                    nss_index += 1

            topic.master_sub_sequence_names = ",".join(
                master_sub_sequence_names)
            topic.nested_sub_sequence_names = ",".join(
                nested_sub_sequence_names)
            topic.nested_sub_sequence_filters = ",".join(
                nested_sub_sequence_filters)

        topic.num_filters = len(
            self.filters) if self.filters is not None else 0
        if topic.num_filters:
            filter_names = []
            exp_index = 0
            for i, v in enumerate(self.filters.values()):
                filter_names.append(v.name)
                topic.bright_limit[i] = v.bright_limit
                topic.dark_limit[i] = v.dark_limit
                topic.max_seeing[i] = v.max_seeing
                topic.num_filter_exposures[i] = len(v.exposures)
                for exposure in v.exposures:
                    topic.exposures[exp_index] = exposure
                    exp_index += 1
            topic.filter_names = ','.join(filter_names)

        topic.max_num_targets = self.scheduling.max_num_targets
        topic.accept_serendipity = self.scheduling.accept_serendipity
        topic.accept_consecutive_visits = self.scheduling.accept_consecutive_visits
        topic.airmass_bonus = self.scheduling.airmass_bonus
        topic.hour_angle_bonus = self.scheduling.hour_angle_bonus
        topic.hour_angle_max = self.scheduling.hour_angle_max
        topic.restart_lost_sequences = self.scheduling.restart_lost_sequences
        topic.restart_complete_sequences = self.scheduling.restart_complete_sequences
        topic.max_visits_goal = self.scheduling.max_visits_goal

        return topic