diff --git a/SAS/TMSS/backend/services/scheduling/lib/constraints.py b/SAS/TMSS/backend/services/scheduling/lib/constraints.py index bde1191b6c2ba820d3c2d2b00f31c26829a7417e..5ffd1595761928b2a4213c24e1a56338df6ad226 100644 --- a/SAS/TMSS/backend/services/scheduling/lib/constraints.py +++ b/SAS/TMSS/backend/services/scheduling/lib/constraints.py @@ -966,7 +966,9 @@ 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) + # keep track of all optimal_start_times for all target-observations and all targets + # if this constraint is met for all, then compute an overall average optimal starttime as final result + optimal_start_times = [] # transits are only computed for target observations target_obs_tasks = [t for t in scheduling_unit.observation_tasks if t.is_target_observation] @@ -997,39 +999,60 @@ 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]) - if logger.level==logging.DEBUG: + if logger.isEnabledFor(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) + logger.debug("SUB id=%s transit='%sUTC' '%sLST' for %s %s near task_proposed_center_time='%s'", scheduling_unit.id, transit_timestamp, transit_timestamp_lst, station, pointing.str_astro(), task_proposed_center_time) + + optimal_task_center_time = transit_timestamp - target_obs_task.relative_start_time + # the optimal start_time is half a duration earlier than the center + optimal_task_start_time = optimal_task_center_time - (target_obs_task.specified_duration / 2) + # if transit_from_limit>0 or transit_to_limit<0, + # then the optimal_task_start_time should be 'pushed' asymetrically to that side, in order to keep the center within the limits. + if transit_from_limit > 0: + optimal_task_start_time -= timedelta(seconds=transit_from_limit) + if transit_to_limit < 0: + optimal_task_start_time += timedelta(seconds=transit_to_limit) + # keep track of all optimal_start_time to compute one final overall average optimal_start_time at the end, together with one score. + optimal_start_times.append(optimal_task_start_time) + + # now check if the constraint is met + # include the margins for gridding effects when checking offset-within-window, + offset = int((task_proposed_center_time-optimal_task_center_time).total_seconds()) + if offset < transit_from_limit_with_margin or offset > transit_to_limit_with_margin: + # constraint not met for this task/target. + result.message = "offset of %s[s] at task_center='%s' from transit at '%s' at %s for %s is not within [%s, %s]" % (offset, task_proposed_center_time, transit_timestamp, station, pointing, transit_from_limit, transit_to_limit) + logger.debug(result) - # transit minus half duration is by definition the optimal start_time - # also take the task relative start time against the su.starttime into account - result.optimal_start_time = transit_timestamp - (target_obs_task.specified_duration / 2) - target_obs_task.relative_start_time + # compute the overall averaged result + if len(optimal_start_times) == 1: + result.optimal_start_time = optimal_start_times[0] + else: + result.optimal_start_time = optimal_start_times[0] + timedelta(seconds=sum([(t-optimal_start_times[0]).total_seconds() for t in optimal_start_times[1:]])/len(optimal_start_times)) - # earliest_possible_start_time is the transit plus the lower limit (which is usually negative) - result.earliest_possible_start_time = result.optimal_start_time + timedelta(seconds=transit_from_limit) + result.optimal_start_time = round_to_second_precision(result.optimal_start_time) - # now check if the constraint is met, and compute/set score - # include the margins for gridding effects when checking offset-within-window, - # but not when computing score - offset = int((task_proposed_center_time-transit_timestamp).total_seconds()) - if offset > transit_from_limit_with_margin and offset < transit_to_limit_with_margin: - # constraint is met. compute score. - # 1.0 when proposed_center_time==transit_timestamp - # 0.0 at either translit offset limit - if offset <= 0: - score = abs(transit_from_limit_with_margin - offset)/abs(transit_from_limit_with_margin) - else: - score = abs(transit_to_limit_with_margin - offset)/abs(transit_to_limit_with_margin) + # earliest_possible_start_time is the transit plus the lower limit (which is usually negative) + result.earliest_possible_start_time = result.optimal_start_time + timedelta(seconds=transit_from_limit) + if transit_to_limit < 0: # nudge asymmetric + result.earliest_possible_start_time -= timedelta(seconds=transit_to_limit) - result.score = min(1.0, max(0.0, score)) - else: - result.score = 0 - result.message = "offset of %s[s] at task_center='%s' from transit at '%s' at %s for %s is not within [%s, %s]" % (offset, task_proposed_center_time, transit_timestamp, station, pointing, transit_from_limit, transit_to_limit) - - # log and early exit, cause the constraint is not met. - logger.debug(result) - return result + if result.message: + # the message was set for one or more failing task/target/station transit_offset constraints + result.score = 0 + else: + # all task/target/station transit_offset constraints were met. + # compute overall score + # 1.0 when proposed_center_time==transit_timestamp + # 0.0 at either translit offset limit + # overall average offset + offset = int((proposed_start_time - result.optimal_start_time).total_seconds()) + if offset <= 0: + score = abs(transit_from_limit - offset) / abs(transit_from_limit) + else: + score = abs(transit_to_limit - offset) / abs(transit_to_limit) + result.score = min(1.0, max(0.0, score)) + logger.debug(result) return result @@ -1405,7 +1428,7 @@ def get_earliest_possible_start_time_for_sky_transit_offset(scheduling_unit: mod if result.is_constraint_met: if result.earliest_possible_start_time > lower_bound: - return result.earliest_possible_start_time + return max(possible_start_time, result.earliest_possible_start_time) return lower_bound # constraint is not met, or before lower_bound... or equal to previous evaluation result @@ -1415,7 +1438,7 @@ def get_earliest_possible_start_time_for_sky_transit_offset(scheduling_unit: mod allow_quick_jump = False # prevent more quick jumps which may lead to an endless back-and-forth loop else: # advance with a step, and evaluate again - possible_start_time += max(timedelta(hours=1), gridder.as_timedelta()) + possible_start_time += max(timedelta(minutes=5), gridder.as_timedelta()) return None