diff --git a/SAS/TMSS/backend/services/scheduling/lib/constraints.py b/SAS/TMSS/backend/services/scheduling/lib/constraints.py index 4b85778380d8d1ce51919f60c37d79fae9a7eb4d..1956722614b27e21a9583be1e7838fde69c62f99 100644 --- a/SAS/TMSS/backend/services/scheduling/lib/constraints.py +++ b/SAS/TMSS/backend/services/scheduling/lib/constraints.py @@ -1000,9 +1000,6 @@ def evaluate_sky_transit_constraint(scheduling_unit: models.SchedulingUnitBluepr logger.warning("SUB id=%s task id=%s could not determine pointing to evaluate sky transit constraint", scheduling_unit.id, target_obs_task.id) result.score = 0 return result - elif transit_pointings[0].direction_type not in ['J2000', 'MOON', 'SUN']: - logger.warning('SUB id=%s task id=%s contains a pointing of unsupported direction_type=%s' % (scheduling_unit.id, target_obs_task.id, transit_pointings[0].direction_type)) - 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 @@ -1013,6 +1010,15 @@ def evaluate_sky_transit_constraint(scheduling_unit: models.SchedulingUnitBluepr # currently we only check at bounds and center, we probably want to add some more samples in between later on for pointing in transit_pointings: + if pointing.direction_type not in ['J2000', 'MOON', 'SUN', 'AZELGEO']: + logger.warning('SUB id=%s task id=%s contains a pointing of unsupported direction_type=%s' % (scheduling_unit.id, target_obs_task.id, pointing.direction_type)) + result.score = 0 + return result + + if pointing.direction_type == 'AZELGEO': + logger.warning('ignoring AZELGEO pointing for transit constraint for SUB id=%s task id=%s pointing_target=%s' % (scheduling_unit.id, target_obs_task.id, pointing.target)) + continue + transits = coordinates_timestamps_and_stations_to_target_transit(pointing=pointing, timestamps=(task_proposed_center_time,), stations=tuple(stations), @@ -1045,6 +1051,12 @@ def evaluate_sky_transit_constraint(scheduling_unit: models.SchedulingUnitBluepr 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) + if len(optimal_start_times) == 0: + result.earliest_possible_start_time = proposed_start_time + result.optimal_start_time = None + result.score = 1 + return result + # compute the overall averaged result if len(optimal_start_times) == 1: result.optimal_start_time = optimal_start_times[0] @@ -1094,10 +1106,6 @@ def evaluate_sky_min_elevation_constraint(scheduling_unit: models.SchedulingUnit return result for task in scheduling_unit.observation_tasks.all(): - if get_target_sap_pointings(task) and get_target_sap_pointings(task)[0].direction_type not in ['J2000', 'MOON', 'SUN']: - logger.warning('SUB id=%s task id=%s contains a pointing of unsupported direction_type=%s' % (scheduling_unit.id, task.id, get_target_sap_pointings(task)[0].direction_type)) - return result - stations = get_stations_to_be_evaluated(task, proposed_start_time, 10e4) # determine the min_elevation and stations depending on observation type @@ -1107,16 +1115,32 @@ def evaluate_sky_min_elevation_constraint(scheduling_unit: models.SchedulingUnit # target imaging, and beamforming or combined observations all use min_elevation.target min_elevation = Angle(constraints['sky']['min_elevation']['target'], unit=astropy.units.rad) for pointing in get_target_sap_pointings(task): - pointings_and_min_elevations.append((pointing, min_elevation)) + if pointing.direction_type not in ['J2000', 'MOON', 'SUN', 'AZELGEO']: + logger.warning('SUB id=%s task id=%s contains a pointing of unsupported direction_type=%s for min_elevation' % (scheduling_unit.id, task.id, pointing.direction_type)) + result.score = 0 + return result + if pointing.direction_type == 'AZELGEO': + logger.info('ignoring AZELGEO pointing for min_elevation constraint for SUB id=%s task id=%s pointing_target=%s' % (scheduling_unit.id, task.id, pointing.target)) + else: + pointings_and_min_elevations.append((pointing, min_elevation)) if task.is_calibrator_observation: # calibrator and/or combined observations all use min_elevation.calibrator min_elevation = Angle(constraints['sky']['min_elevation']['calibrator'], unit=astropy.units.rad) for pointing in get_calibrator_sap_pointings(task): - pointings_and_min_elevations.append((pointing, min_elevation)) + if pointing.direction_type not in ['J2000', 'MOON', 'SUN', 'AZELGEO']: + logger.warning('SUB id=%s task id=%s contains a pointing of unsupported direction_type=%s for min_elevation' % (scheduling_unit.id, task.id, pointing.direction_type)) + result.score = 0 + return result + if pointing.direction_type == 'AZELGEO': + logger.info('ignoring AZELGEO pointing for min_elevation constraint for SUB id=%s task id=%s pointing_target=%s' % (scheduling_unit.id, task.id, pointing.target)) + else: + pointings_and_min_elevations.append((pointing, min_elevation)) if not pointings_and_min_elevations: - logger.warning("SUB id=%s task id=%s could not determine pointing to evaluate sky constraints", scheduling_unit.id, task.id) + logger.debug("SUB id=%s task id=%s could not determine pointing to evaluate sky min_elevation constraints", scheduling_unit.id, task.id) + result.earliest_possible_start_time = proposed_start_time + result.score = 1 return result # evaluate at least at the gridded start/stop of observation @@ -1251,6 +1275,9 @@ def get_timestamps_elevations_and_offset_to_transit(scheduling_unit: models.Sche offsets_to_transits = [] lowest_elevation = 1e99 for pointing in transit_pointings: + if pointing.direction_type == 'AZELGEO': + continue + elevation_at_start = compute_elevation(pointing, gridded_start_time, station) elevation_at_stop = compute_elevation(pointing, gridded_stop_time, station) elevation_at_center = compute_elevation(pointing, gridded_center_time, station) @@ -1317,6 +1344,10 @@ def evaluate_sky_min_distance_constraint(scheduling_unit: models.SchedulingUnitB # 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: + if pointing.direction_type == 'AZELGEO': + logger.debug('ignoring AZELGEO pointing for min_distance constraint for SUB id=%s task id=%s pointing_target=%s' % (scheduling_unit.id, obs_task.id, pointing.target)) + continue + for body, min_distance in constraints.items(): for gridded_timestamp in gridded_timestamps: distances = coordinates_and_timestamps_to_separation_from_bodies(pointing=pointing, @@ -1356,7 +1387,7 @@ def evaluate_sky_min_distance_constraint(scheduling_unit: models.SchedulingUnitB # largest possible distance is half a great-circle, so Pi radians. Should yield a score of 0. # smallest possible distance is 0. Should yield a score of 1. # so just normalize the smallest_actual_distance over Pi - result.score = 1.0 - (smallest_actual_distance/3.1415) + result.score = (1.0 - (smallest_actual_distance/3.1415)) if smallest_actual_distance < 3.14 else 1 # for min_distance there is no optimal start time. # any timestamp meeting the constraint is good enough. @@ -1990,7 +2021,7 @@ def get_optimal_start_time(scheduling_unit: models.SchedulingUnitBlueprint, lowe # seek nearest transit around lower_bound result = evaluate_sky_transit_constraint(scheduling_unit, gridder.grid_time(lower_bound), gridder=gridder, which='nearest') if not result.is_constraint_met and result.optimal_start_time is None: - # could not determine an optimal starttime (maybe no stations are available?) + # could not determine an optimal starttime (maybe no stations are available?, or azelgeo pointing?) return None optimal_start_time = result.optimal_start_time @@ -2005,7 +2036,8 @@ def get_optimal_start_time(scheduling_unit: models.SchedulingUnitBlueprint, lowe optimal_start_time = get_earliest_possible_start_time(scheduling_unit, lower_bound, lower_bound+timedelta(hours=24), gridder=gridder) logger.debug("get_optimal_start_time SUB id=%s earliest_possible_start_time and optimal_start_time='%s'", scheduling_unit.id, optimal_start_time) - assert optimal_start_time is not None and optimal_start_time >= lower_bound, "SUB id=%s cannot find optimal start_time > %s" % (scheduling_unit.id, lower_bound) + if optimal_start_time is None: + return None optimal_start_time = round_to_second_precision(optimal_start_time) logger.debug("get_optimal_start_time SUB id=%s lower_bound='%s' optimal_start_time='%s' (took %.1f[s])", scheduling_unit.id, lower_bound, optimal_start_time, (datetime.utcnow() - _method_start_timestamp).total_seconds()) diff --git a/SAS/TMSS/backend/services/scheduling/lib/dynamic_scheduling.py b/SAS/TMSS/backend/services/scheduling/lib/dynamic_scheduling.py index a17f1dcadb6fd71398d81d46983fb534d70332e6..e732a13e8e0e2be64de393dbffa539423db94725 100644 --- a/SAS/TMSS/backend/services/scheduling/lib/dynamic_scheduling.py +++ b/SAS/TMSS/backend/services/scheduling/lib/dynamic_scheduling.py @@ -955,7 +955,16 @@ class Scheduler: Angle(lowest_elevation, astropy.units.rad).degree if lowest_elevation else None, '/'.join(str(q) for q in unit.main_observation_task.used_station_counts)) except Exception as e: - logger.warning(e) + logger.log(log_level, + " id=% 4d %s %s %s start_time='%s'[UTC] dur=%4d[min] %s name=%s", + unit.id, + ("'%s'" % (unit.project.name[:8],)).ljust(10), + unit.priority_queue.value, + 'D' if unit.is_dynamically_scheduled else 'F', + round_to_second_precision(unit.scheduled_observation_start_time), + round(unit.specified_main_observation_duration.total_seconds()/60.0), + unit.status.value.ljust(14), + ("'%s'" % (unit.name[:32],)).ljust(34)) logger.log(log_level, "-----------------------------------------------------------------") # TODO: report on schedule density except Exception as e: diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/adapters/plots.py b/SAS/TMSS/backend/src/tmss/tmssapp/adapters/plots.py index 15a061036f63b1bdda5595d663b5899cca1401b7..61ac7b8f0e9ba1dfbf936f19d33ab328dfefed01 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/adapters/plots.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/adapters/plots.py @@ -243,7 +243,12 @@ def scheduling_constraints_plot(scheduling_units: Union[models.SchedulingUnitBlu # plot an elevation line chart per pointing for pointing in pointings: - obs_elevations = [compute_elevation(pointing=pointing, timestamp=timestamp, station=station) * 180.0 / 3.1415 for timestamp in obs_timestamps] + try: + obs_elevations = [compute_elevation(pointing=pointing, timestamp=timestamp, station=station) * 180.0 / 3.1415 for timestamp in obs_timestamps] + except ValueError: + # plot elevation for non-J2000 pointings as 0, so we at least have an entry in the legend. + obs_elevations = [0 for _ in obs_timestamps] + # simple line plot elevation_axes.plot(obs_timestamps, obs_elevations, label=pointing.str_astro() + (" Cal" if pointing in cal_pointings else ''), @@ -291,6 +296,9 @@ def scheduling_constraints_plot(scheduling_units: Union[models.SchedulingUnitBlu # for target observation(s) also annotate the transit info (transit timestamp in UTC & LST, and elevation) for p_idx, pointing in enumerate(get_transit_offset_pointings(obs_task)): + if pointing.direction_type == 'AZELGEO': + continue + transit_timestamp = coordinates_timestamp_and_station_to_target_transit(pointing, obs_center_timestamp, observer, n_grid_points=200) # plot the limits within which this transit for this pointing should occur diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/conversions.py b/SAS/TMSS/backend/src/tmss/tmssapp/conversions.py index 4e5d23476d06cd96299f19175861e5499326e195..59959ec16da6f6908a656b1c17d168ad3ec6b9fe 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/conversions.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/conversions.py @@ -59,7 +59,7 @@ class Pointing(): def str_astro(self): '''represent the pointing in astropy hmsdms, direction_type, targetname''' - return "%s%s%s" % (self.as_SkyCoord().to_string('hmsdms'), + return "%s%s%s" % ('%.3f°, %.3f°' % (astropy.coordinates.Angle(self.angle1, unit=astropy.units.rad).deg, astropy.coordinates.Angle(self.angle2, unit=astropy.units.rad).deg) if self.direction_type=='AZELGEO' else self.as_SkyCoord().to_string('hmsdms'), '' if self.direction_type=='J2000' else self.direction_type, (" '"+self.target+"'") if self.target else "")