From f855ec7537b2deb5f96e00f8b09f302bcf5e975b Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 12 Jun 2023 16:56:25 +0200 Subject: [PATCH] TMSS-2582: bonus, performance boost. early exit when a unit has no earliest possible starttime. --- .../services/scheduling/lib/constraints.py | 45 ++++++++++++------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/SAS/TMSS/backend/services/scheduling/lib/constraints.py b/SAS/TMSS/backend/services/scheduling/lib/constraints.py index 3445b7d87a9..e229533c8b1 100644 --- a/SAS/TMSS/backend/services/scheduling/lib/constraints.py +++ b/SAS/TMSS/backend/services/scheduling/lib/constraints.py @@ -833,6 +833,7 @@ def evaluate_sky_transit_constraint(scheduling_unit: models.SchedulingUnitBluepr transit_from_limit_with_margin = transit_from_limit - 60*gridder.grid_minutes transit_to_limit_with_margin = transit_to_limit + 60*gridder.grid_minutes + logger.debug("evaluate_sky_transit_constraint: SUB id=%s proposed_start_time='%s'", scheduling_unit.id, proposed_start_time) # transits are only computed for target observations target_obs_tasks = [t for t in scheduling_unit.observation_tasks if t.is_target_observation] @@ -849,7 +850,7 @@ def evaluate_sky_transit_constraint(scheduling_unit: models.SchedulingUnitBluepr # 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_center_time = round_to_second_precision(task_proposed_start_time + (target_obs_task.specified_duration / 2)) stations = get_schedulable_stations(target_obs_task, task_proposed_start_time) stations = get_unique_sorted_boundary_stations_and_cs002(stations, 50e4) @@ -864,7 +865,9 @@ def evaluate_sky_transit_constraint(scheduling_unit: models.SchedulingUnitBluepr for station, transit_timestamps in transits.items(): assert len(transit_timestamps) == 1 # only one center time transit_timestamp = round_to_second_precision(transit_timestamps[0]) - logger.debug("SUB id=%s transit='%s' for %s %s", scheduling_unit.id, transit_timestamp, station, pointing) + if logger.level==logging.DEBUG: + transit_timestamp_lst = local_sidereal_time_for_utc_and_station(transit_timestamp, station) + logger.debug("SUB id=%s transit='%sUTC' '%sLST' for %s %s task_proposed_center_time='%s'", scheduling_unit.id, transit_timestamp, transit_timestamp_lst, station, pointing.str_astro(), task_proposed_center_time) # transit minus half duration is by definition the optimal start_time # also take the task relative start time against the su.starttime into account @@ -1209,7 +1212,8 @@ def get_earliest_possible_start_time_for_sky_min_elevation(scheduling_unit: mode logger.debug('get_earliest_possible_start_time_for_sky_min_elevation %s', result) if not result.has_constraint: - return None + # if there is no constraint, the earliest_possible_start_time is just right away, at the lower_bound + return lower_bound if result.is_constraint_met: return result.earliest_possible_start_time @@ -1252,14 +1256,15 @@ def get_earliest_possible_start_time_for_sky_transit_offset(scheduling_unit: mod logger.debug('get_earliest_possible_start_time_for_sky_transit_offset %s', result) if not result.has_constraint: - return None + # if there is no constraint, the earliest_possible_start_time is just right away, at the lower_bound + return lower_bound if result.is_constraint_met: if result.earliest_possible_start_time > lower_bound: return result.earliest_possible_start_time return lower_bound - # constraint is not met, or before lower_bound... or equal to previous evaulation result + # constraint is not met, or before lower_bound... or equal to previous evaluation result if result.earliest_possible_start_time is not None and result.earliest_possible_start_time >= lower_bound and allow_quick_jump: # quick jump to earliest_possible_start_time and evaluate to confirm that constraint is met. possible_start_time = result.earliest_possible_start_time @@ -1290,7 +1295,8 @@ def get_earliest_possible_start_time_for_sky_min_distance(scheduling_unit: model logger.debug('get_earliest_possible_start_time_for_sky_min_distance %s', result) if not result.has_constraint: - return None + # if there is no constraint, the earliest_possible_start_time is just right away, at the lower_bound + return lower_bound if result.is_constraint_met: return result.earliest_possible_start_time @@ -1505,7 +1511,8 @@ def get_earliest_possible_start_time_for_daily_constraints(scheduling_unit: mode logger.debug('get_earliest_possible_start_time_for_daily_constraints %s', result) if not result.has_constraint: - return None + # if there is no constraint, the earliest_possible_start_time is just right away, at the lower_bound + return lower_bound if not result.is_constraint_met and result.earliest_possible_start_time is not None: # advance straight to earliest_possible_start_time, and evaluate again to ensure the constraint is met @@ -1560,6 +1567,7 @@ def get_earliest_possible_start_time_for_time_constraints(scheduling_unit: model from_timestamps = [parser.parse(between["from"], ignoretz=True) for between in constraints['time']['between'] if lower_bound is None or parser.parse(between["to"], ignoretz=True) > lower_bound] + from_timestamps = [max(lower_bound, ts) for ts in from_timestamps] if from_timestamps: earliest_possible_start_times.add(min(from_timestamps)) @@ -1585,8 +1593,8 @@ def get_earliest_possible_start_time_for_time_constraints(scheduling_unit: model if not earliest_possible_start_times and not (constraints.get('time', {}).get('at') or constraints.get('time', {}).get('before') or constraints.get('time', {}).get('after') or - constraints.get('time',{}).get('between') or - constraints.get('time',{}).get('not_between')): + constraints.get('time', {}).get('between') or + constraints.get('time', {}).get('not_between')): # an empty time constraint means it can just run at/after lower_bound return lower_bound @@ -1594,7 +1602,8 @@ def get_earliest_possible_start_time_for_time_constraints(scheduling_unit: model earliest_possible_start_times = [t for t in earliest_possible_start_times if t >= lower_bound] if upper_bound is not None: - earliest_possible_start_times = [t for t in earliest_possible_start_times if t <= upper_bound] + start_before = upper_bound - scheduling_unit.specified_observation_duration + earliest_possible_start_times = [t for t in earliest_possible_start_times if t <= start_before] if earliest_possible_start_times: return max(earliest_possible_start_times) @@ -1637,8 +1646,11 @@ def get_earliest_possible_start_time(scheduling_unit: models.SchedulingUnitBluep get_earliest_possible_start_time_for_sky_min_distance): try: earliest_possible_start_time = get_earliest_possible_start_time_method(scheduling_unit, lower_bound, upper_bound, gridder, raise_if_interruped) - if earliest_possible_start_time is not None: - earliest_possible_start_times.add(earliest_possible_start_time) + if earliest_possible_start_time is None: + # early exit. None result means no possible starttime at all for this unit with these constraints in this time window. + logger.debug("get_earliest_possible_start_time SUB id=%s window=['%s', '%s'] early exit returning None, because %s returned None", scheduling_unit.id, lower_bound, upper_bound, get_earliest_possible_start_time_method.__name__) + return None + earliest_possible_start_times.add(earliest_possible_start_time) except SchedulerInterruptedException: raise except Exception as e: @@ -1647,13 +1659,15 @@ def get_earliest_possible_start_time(scheduling_unit: models.SchedulingUnitBluep if len(earliest_possible_start_times) == 0: # it's possible that none of the above constraints yielded an earliest_possible_start_time (or that there are no constraints) # this might mean that the unit can start right away at the lower_bound - # so, always add lower_bound, and evaluate it below (along with the other possible earliest_possible_start_times) if it can actually run. - earliest_possible_start_times.add(lower_bound) + # so, add lower_bound if the unit can directly run at lower_bound. + if can_run_at(scheduling_unit, lower_bound, gridder): + earliest_possible_start_times.add(lower_bound) # filter for non-None and within bound(s) earliest_possible_start_times = set([t for t in earliest_possible_start_times if t is not None and t >= lower_bound]) if upper_bound is not None: - earliest_possible_start_times = set([t for t in earliest_possible_start_times if t <= upper_bound]) + start_before = upper_bound - scheduling_unit.specified_observation_duration + earliest_possible_start_times = set([t for t in earliest_possible_start_times if t <= start_before]) logger.debug("get_earliest_possible_start_time SUB id=%s lower_bound='%s' earliest_possible_start_times per constraint: %s", scheduling_unit.id, lower_bound, ', '.join([str(t) for t in sorted(earliest_possible_start_times)])) # the earliest_possible_start_times were computed per constraint-type. @@ -1887,6 +1901,7 @@ def get_min_earliest_possible_start_time(scheduling_units: [models.SchedulingUni if earliest_possible_start_time is not None: if min_earliest_possible_start_time is None or earliest_possible_start_time < min_earliest_possible_start_time: min_earliest_possible_start_time = earliest_possible_start_time + logger.debug("get_min_earliest_possible_start_time returning '%s' for window=['%s', '%s'] and unit_ids=%s", min_earliest_possible_start_time, lower_bound, upper_bound, ','.join(str(s.id) for s in scheduling_units)) return min_earliest_possible_start_time -- GitLab