diff --git a/SAS/TMSS/backend/services/scheduling/lib/constraints.py b/SAS/TMSS/backend/services/scheduling/lib/constraints.py index 6bdc533b42d292a51b054b595b5af86b6a716d88..9632d84f2352315af5d3fb4a5ea4ccd9d021e088 100644 --- a/SAS/TMSS/backend/services/scheduling/lib/constraints.py +++ b/SAS/TMSS/backend/services/scheduling/lib/constraints.py @@ -1275,66 +1275,76 @@ def evaluate_sky_min_distance_constraint(scheduling_unit: models.SchedulingUnitB """ Evaluate the sky min_distance constraint: is it met? and what are the score, optimal- and earliest_possible_start_time? """ result = ConstraintResult(scheduling_unit, 'sky.min_distance', proposed_start_time) - constraints = scheduling_unit.scheduling_constraints_doc + constraints_doc = scheduling_unit.scheduling_constraints_doc - if 'sky' not in constraints or 'min_distance' not in constraints['sky']: + if 'sky' not in constraints_doc or 'min_distance' not in constraints_doc['sky']: return result + # disect the constraints in a 'target' and 'calibrator' part which works for all versions + # In TMSS-2877, constraints template version 9 was created with two sections for 'target' and 'calibrator'. + # before version 9, we only had one section which was for the target only. + target_constraints = constraints_doc['sky']['min_distance'].get('target', constraints_doc['sky']['min_distance']) + calibrator_constraints = constraints_doc['sky']['min_distance'].get('calibrator', {}) + # keep track of the smallest actual distance to compute the score smallest_actual_distance = 3.1415 # bodies can be at most half a great circle away, which is Pi radians. - # min_distance constraints are only computed for target observations target_obs_tasks = [t for t in scheduling_unit.observation_tasks if t.is_target_observation] - for target_obs_task in target_obs_tasks: - sap_pointings = get_target_sap_pointings(target_obs_task) - if not sap_pointings: - logger.warning("SUB id=%s task id=%s could not determine pointing(s) to evaluate sky constraints", scheduling_unit.id, target_obs_task.id) - return result - - # since the constraint only applies to the middle of the obs, only consider the proposed_center_time - # take along the relative_start_time of this task compared to the scheduling unit's start_time - task_proposed_start_time = proposed_start_time + target_obs_task.relative_start_time - task_proposed_center_time = task_proposed_start_time + target_obs_task.specified_duration / 2 - task_proposed_end_time = task_proposed_start_time + target_obs_task.specified_duration - gridded_timestamps = (gridder.grid_time(task_proposed_start_time), - gridder.grid_time(task_proposed_center_time), - gridder.grid_time(task_proposed_end_time)) - - # currently we only check at bounds and center, we probably want to add some more samples in between later on - # loop over all bodies and their respective min_distance constraints - for pointing in sap_pointings: - for body, min_distance in constraints['sky']['min_distance'].items(): - for gridded_timestamp in gridded_timestamps: - distances = coordinates_and_timestamps_to_separation_from_bodies(pointing=pointing, - timestamps=(gridded_timestamp,), - bodies=[body]) - actual_distances = distances[body] - assert (len(actual_distances) == 1) - actual_distance = actual_distances[gridded_timestamp] - # logger.debug("min_distance: SUB id=%s task_id=%s task_name='%s' pointing='%s' distance=%.3f[deg] to body=%s %s min_distance=%.3f[deg] at '%s'", - # scheduling_unit.id, target_obs_task.id, target_obs_task.name, pointing, actual_distance.degree, body, - # '<' if actual_distance.rad < min_distance else '>=', - # Angle(min_distance, astropy.units.rad).degree, - # gridded_timestamp) - - # keep track of the smallest actual distance to compute the score - smallest_actual_distance = min(smallest_actual_distance, actual_distance.rad) - - if actual_distance.rad < min_distance: - # constraint not met. update result, and do early exit. - result.score = 0 - result.earliest_possible_start_time = None - result.optimal_start_time = None - result.message = "%s distance=%.3f[deg] to body=%s %s min_distance=%.3f[deg] at '%s'" % (pointing, - actual_distance.degree, - body, - '<' if actual_distance.rad < min_distance else '>=', - Angle(min_distance, astropy.units.rad).degree, - gridded_timestamp) - logger.debug(result) - return result - # no early exit, so constraint is met for this station and timestamp - # continue with rest of stations & timestamps + calibrator_obs_tasks = [t for t in scheduling_unit.observation_tasks if t.is_calibrator_observation] + + for constraints, obs_tasks in ((target_constraints, target_obs_tasks), + (calibrator_constraints, calibrator_obs_tasks)): + # min_distance constraints are only computed for target observations + for obs_task in obs_tasks: + sap_pointings = get_target_sap_pointings(obs_task) if obs_task.is_target_observation else get_calibrator_sap_pointings(obs_task) + if not sap_pointings: + logger.warning("SUB id=%s task id=%s could not determine pointing(s) to evaluate sky constraints", scheduling_unit.id, target_obs_task.id) + return result + + # since the constraint only applies to the middle of the obs, only consider the proposed_center_time + # take along the relative_start_time of this task compared to the scheduling unit's start_time + task_proposed_start_time = proposed_start_time + obs_task.relative_start_time + task_proposed_center_time = task_proposed_start_time + obs_task.specified_duration / 2 + task_proposed_end_time = task_proposed_start_time + obs_task.specified_duration + gridded_timestamps = (gridder.grid_time(task_proposed_start_time), + gridder.grid_time(task_proposed_center_time), + gridder.grid_time(task_proposed_end_time)) + + # currently we only check at bounds and center, we probably want to add some more samples in between later on + # loop over all bodies and their respective min_distance constraints + for pointing in sap_pointings: + for body, min_distance in constraints.items(): + for gridded_timestamp in gridded_timestamps: + distances = coordinates_and_timestamps_to_separation_from_bodies(pointing=pointing, + timestamps=(gridded_timestamp,), + bodies=[body]) + actual_distances = distances[body] + assert (len(actual_distances) == 1) + actual_distance = actual_distances[gridded_timestamp] + # logger.debug("min_distance: SUB id=%s task_id=%s task_name='%s' pointing='%s' distance=%.3f[deg] to body=%s %s min_distance=%.3f[deg] at '%s'", + # scheduling_unit.id, target_obs_task.id, target_obs_task.name, pointing, actual_distance.degree, body, + # '<' if actual_distance.rad < min_distance else '>=', + # Angle(min_distance, astropy.units.rad).degree, + # gridded_timestamp) + + # keep track of the smallest actual distance to compute the score + smallest_actual_distance = min(smallest_actual_distance, actual_distance.rad) + + if actual_distance.rad < min_distance: + # constraint not met. update result, and do early exit. + result.score = 0 + result.earliest_possible_start_time = None + result.optimal_start_time = None + result.message = "%s distance=%.3f[deg] to body=%s %s min_distance=%.3f[deg] at '%s'" % (pointing, + actual_distance.degree, + body, + '<' if actual_distance.rad < min_distance else '>=', + Angle(min_distance, astropy.units.rad).degree, + gridded_timestamp) + logger.debug(result) + return result + # no early exit, so constraint is met for this station and timestamp + # continue with rest of stations & timestamps # no early exit, so constraint is met for all stations and timestamps result.earliest_possible_start_time = proposed_start_time