diff --git a/SAS/TMSS/backend/services/scheduling/test/t_dynamic_scheduling.py b/SAS/TMSS/backend/services/scheduling/test/t_dynamic_scheduling.py index 30c4809383f17f2ba047f49e3964a21daf608920..aab4ab7b6d15f9622d6c5f1e4014ca9711a33a4d 100755 --- a/SAS/TMSS/backend/services/scheduling/test/t_dynamic_scheduling.py +++ b/SAS/TMSS/backend/services/scheduling/test/t_dynamic_scheduling.py @@ -207,643 +207,643 @@ class TestDynamicScheduling(TestCase): # Note: we use django.test.TestCase inst self.assertEqual(scheduled_scheduling_unit.status, 'scheduled') self.assertEqual(scheduled_scheduling_unit.start_time, at) -# @unittest.skip("FIX TEST, skipping it for now, see TODO comment in assign_start_stop_times_to_schedulable_scheduling_units") -# def test_three_simple_observations_no_constraints_different_project_priority(self): -# scheduling_unit_draft_low = self.create_simple_observation_scheduling_unit("scheduling unit low", scheduling_set=self.scheduling_set_low) -# scheduling_unit_blueprint_low = create_task_blueprints_and_subtasks_from_scheduling_unit_draft(scheduling_unit_draft_low) -# -# scheduling_unit_draft_medium = self.create_simple_observation_scheduling_unit("scheduling unit medium", scheduling_set=self.scheduling_set_medium) -# scheduling_unit_blueprint_medium = create_task_blueprints_and_subtasks_from_scheduling_unit_draft(scheduling_unit_draft_medium) -# -# scheduling_unit_draft_high = self.create_simple_observation_scheduling_unit("scheduling unit high", scheduling_set=self.scheduling_set_high) -# scheduling_unit_blueprint_high = create_task_blueprints_and_subtasks_from_scheduling_unit_draft(scheduling_unit_draft_high) -# -# # call the method-under-test. -# scheduled_scheduling_unit = do_dynamic_schedule() -# -# # we expect the scheduling_unit with the highest project rank to be scheduled first -# self.assertIsNotNone(scheduled_scheduling_unit) -# self.assertEqual(scheduling_unit_blueprint_high.id, scheduled_scheduling_unit.id) -# -# # check the results -# # we expect the sub_high to be scheduled -# scheduling_unit_blueprint_low.refresh_from_db() -# scheduling_unit_blueprint_medium.refresh_from_db() -# scheduling_unit_blueprint_high.refresh_from_db() -# self.assertEqual(scheduling_unit_blueprint_low.status, 'schedulable') -# self.assertEqual(scheduling_unit_blueprint_medium.status, 'schedulable') -# self.assertEqual(scheduling_unit_blueprint_high.status, 'scheduled') -# -# # check the scheduled subtask -# upcoming_scheduled_subtasks = models.Subtask.objects.filter(state__value='scheduled', -# task_blueprint__scheduling_unit_blueprint__in=(scheduling_unit_blueprint_low, -# scheduling_unit_blueprint_medium, -# scheduling_unit_blueprint_high)).all() -# self.assertEqual(1, upcoming_scheduled_subtasks.count()) -# self.assertEqual(scheduling_unit_blueprint_high.id, upcoming_scheduled_subtasks[0].task_blueprint.scheduling_unit_blueprint.id) -# -# # check scheduling_unit_blueprint_low starts after the scheduled scheduling_unit_blueprint_high -# self.assertGreater(scheduling_unit_blueprint_low.start_time, scheduling_unit_blueprint_medium.start_time) -# self.assertGreater(scheduling_unit_blueprint_medium.start_time, scheduling_unit_blueprint_high.start_time) -# -# # ensure DEFAULT_INTER_OBSERVATION_GAP between them -# self.assertGreaterEqual(scheduling_unit_blueprint_medium.start_time - scheduling_unit_blueprint_high.stop_time, DEFAULT_INTER_OBSERVATION_GAP) -# self.assertGreaterEqual(scheduling_unit_blueprint_low.start_time - scheduling_unit_blueprint_medium.stop_time, DEFAULT_INTER_OBSERVATION_GAP) -# -# def test_time_bound_unit_wins_even_at_lower_priority(self): -# # create two schedule units, one with high one with low prio. -# # first create them without any further constraints, and check if high prio wins. -# scheduling_unit_draft_low = self.create_simple_observation_scheduling_unit("scheduling unit low", scheduling_set=self.scheduling_set_low) -# scheduling_unit_blueprint_low = create_task_blueprints_and_subtasks_from_scheduling_unit_draft(scheduling_unit_draft_low) -# -# scheduling_unit_draft_high = self.create_simple_observation_scheduling_unit("scheduling unit high", scheduling_set=self.scheduling_set_high) -# scheduling_unit_blueprint_high = create_task_blueprints_and_subtasks_from_scheduling_unit_draft(scheduling_unit_draft_high) -# -# now = datetime.utcnow() -# tomorrow = now+timedelta(days=1) -# -# # call the method-under-test. -# best_scored_scheduling_unit = find_best_next_schedulable_unit([scheduling_unit_blueprint_low, scheduling_unit_blueprint_high], now, tomorrow) -# -# # we expect the scheduling_unit with the highest project rank to be scheduled first -# self.assertEqual(scheduling_unit_blueprint_high.id, best_scored_scheduling_unit.scheduling_unit.id) -# -# #now update the low prio unit with a time constraint, "forcing" it to be run in a very thight upcoming time window. -# scheduling_unit_draft_low.scheduling_constraints_doc['time'] = { 'before': (now+scheduling_unit_draft_low.duration).isoformat()+'Z' } -# scheduling_unit_draft_low.save() -# scheduling_unit_blueprint_low.refresh_from_db() -# -# # call the method-under-test. -# best_scored_scheduling_unit = find_best_next_schedulable_unit([scheduling_unit_blueprint_low, scheduling_unit_blueprint_high], now, tomorrow) -# -# # now we expect the scheduling_unit with the lowest project rank to be scheduled first because it can only run within this limited timewindow -# self.assertEqual(scheduling_unit_draft_low.id, best_scored_scheduling_unit.scheduling_unit.id) -# -# -# # update the low prio unit. enlarge the time window constraint a bit, so both low and high prio units can fit -# # this should result that the high prio goes first, and the low prio (which now fits as well) goes second -# scheduling_unit_draft_low.scheduling_constraints_doc['time'] = \ -# { 'before': (now+scheduling_unit_draft_low.duration+scheduling_unit_draft_high.duration).isoformat()+'Z' } -# scheduling_unit_draft_low.save() -# scheduling_unit_blueprint_low.refresh_from_db() -# -# # call the method-under-test. -# best_scored_scheduling_unit = find_best_next_schedulable_unit([scheduling_unit_blueprint_low, scheduling_unit_blueprint_high], now, tomorrow) -# -# # now we expect the scheduling_unit with the lowest project rank to be scheduled first because it can only -# # run within this limited timewindow -# self.assertEqual(scheduling_unit_blueprint_low.id, best_scored_scheduling_unit.scheduling_unit.id) -# -# # call the method-under-test again but search after first unit (should return low prio unit) -# stop_time_of_first = best_scored_scheduling_unit.start_time + best_scored_scheduling_unit.scheduling_unit.duration -# best_scored_scheduling_unit = find_best_next_schedulable_unit([scheduling_unit_blueprint_low, scheduling_unit_blueprint_high], stop_time_of_first, tomorrow) -# self.assertEqual(scheduling_unit_blueprint_low.id, best_scored_scheduling_unit.scheduling_unit.id) -# -# def test_manual_constraint_is_preventing_scheduling_unit_from_being_scheduled_dynamically(self): -# scheduling_unit_draft_manual = self.create_simple_observation_scheduling_unit("scheduling unit manual low", scheduling_set=self.scheduling_set_low, -# constraints={'scheduler': 'manual'}) -# scheduling_unit_blueprint_manual = create_task_blueprints_and_subtasks_from_scheduling_unit_draft(scheduling_unit_draft_manual) -# self.assertEqual(scheduling_unit_blueprint_manual.status, "schedulable") -# -# # call the method-under-test. -# scheduled_scheduling_unit = do_dynamic_schedule() -# -# # we expect no scheduling_unit to be scheduled, because the only one is set to 'manual' constraint -# self.assertIsNone(scheduled_scheduling_unit) -# -# # check the results -# scheduling_unit_blueprint_manual.refresh_from_db() -# self.assertEqual(scheduling_unit_blueprint_manual.status, 'schedulable') -# -# @unittest.skip("FIX TEST, skipping it for now,...something with manual scheduler ?") -# def test_manually_scheduled_blocking_dynamically_scheduled(self): -# scheduling_unit_draft_manual = self.create_simple_observation_scheduling_unit("scheduling unit manual low", scheduling_set=self.scheduling_set_low, -# constraints={'scheduler': 'manual'}) -# scheduling_unit_blueprint_manual = create_task_blueprints_and_subtasks_from_scheduling_unit_draft(scheduling_unit_draft_manual) -# self.assertEqual(scheduling_unit_blueprint_manual.status, "schedulable") -# -# schedule_independent_subtasks_in_scheduling_unit_blueprint(scheduling_unit_blueprint_manual, datetime.utcnow()) -# self.assertEqual(scheduling_unit_blueprint_manual.status, "scheduled") -# -# scheduling_unit_draft_high = self.create_simple_observation_scheduling_unit("scheduling unit online high", scheduling_set=self.scheduling_set_high) -# scheduling_unit_blueprint_high = create_task_blueprints_and_subtasks_from_scheduling_unit_draft(scheduling_unit_draft_high) -# -# # call the method-under-test. -# scheduled_scheduling_unit = do_dynamic_schedule() -# -# # we expect the no scheduling_unit to be scheduled, because the manual is in the way -> Fix it -# self.assertIsNone(scheduled_scheduling_unit) -# -# # check the results -# # we expect the sub_high to be scheduled -# scheduling_unit_blueprint_high.refresh_from_db() -# self.assertEqual(scheduling_unit_blueprint_high.status, 'schedulable') -# -# # check scheduling_unit_blueprint_low starts after the scheduled scheduling_unit_blueprint_high -# self.assertGreater(scheduling_unit_blueprint_high.start_time, scheduling_unit_blueprint_manual.start_time) -# -# # ensure DEFAULT_INTER_OBSERVATION_GAP between them -# self.assertGreaterEqual(scheduling_unit_blueprint_high.start_time - scheduling_unit_blueprint_manual.stop_time, DEFAULT_INTER_OBSERVATION_GAP) -# -# -# class TestDailyConstraints(TestCase): -# ''' -# Tests for the constraint checkers used in dynamic scheduling -# ''' -# -# def setUp(self) -> None: -# # scheduling unit -# self.obs_duration = 120 * 60 -# scheduling_set = models.SchedulingSet.objects.create(**SchedulingSet_test_data()) -# scheduling_unit_draft = TestDynamicScheduling.create_simple_observation_scheduling_unit("scheduling unit for ...%s" % self._testMethodName[30:], -# scheduling_set=scheduling_set, -# obs_duration=self.obs_duration) -# self.scheduling_unit_blueprint = create_task_blueprints_and_subtasks_from_scheduling_unit_draft(scheduling_unit_draft) -# -# # mock out conversions for speedup and assertable timestamps -# # earliest_start_time requests timestamp and timestamp+1day -# self.sunrise_data = { -# 'CS001': {"sunrise": [{"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)}, {"start": datetime(2020, 1, 2, 7, 30, 0), "end": datetime(2020, 1, 2, 9, 30, 0)}], -# "day": [{"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}, {"start": datetime(2020, 1, 2, 9, 30, 0), "end": datetime(2020, 1, 2, 15, 30, 0)}], -# "sunset": [{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)},{"start": datetime(2020, 1, 2, 15, 30, 0), "end": datetime(2020, 1, 2, 17, 30, 0)}], -# "night": [{"start": datetime(2020, 1, 1, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}, {"start": datetime(2020, 1, 2, 17, 30, 0), "end": datetime(2020, 1, 3, 7, 30, 0)}]}, -# 'DE601': {"sunrise": [{"start": datetime(2020, 1, 1, 7, 45, 0), "end": datetime(2020, 1, 1, 9, 45, 0)}, {"start": datetime(2020, 1, 2, 7, 45, 0), "end": datetime(2020, 1, 2, 9, 45, 0)}], -# "day": [{"start": datetime(2020, 1, 1, 9, 45, 0), "end": datetime(2020, 1, 1, 15, 45, 0)}, {"start": datetime(2020, 1, 2, 9, 45, 0), "end": datetime(2020, 1, 2, 15, 45, 0)}], -# "sunset": [{"start": datetime(2020, 1, 1, 15, 45, 0), "end": datetime(2020, 1, 1, 17, 45, 0)}, {"start": datetime(2020, 1, 2, 15, 45, 0), "end": datetime(2020, 1, 2, 17, 45, 0)}], -# "night": [{"start": datetime(2020, 1, 1, 17, 45, 0), "end": datetime(2020, 1, 2, 7, 45, 0)}, {"start": datetime(2020, 1, 2, 17, 45, 0), "end": datetime(2020, 1, 3, 7, 45, 0)}]}} -# -# # variant for timestamp before sunrise, which returns the previous night -# self.sunrise_data_early_night = { -# 'CS001': {"sunrise": [{"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)}, {"start": datetime(2020, 1, 2, 7, 30, 0), "end": datetime(2020, 1, 2, 9, 30, 0)}], -# "day": [{"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}, {"start": datetime(2020, 1, 2, 9, 30, 0), "end": datetime(2020, 1, 2, 15, 30, 0)}], -# "sunset": [{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)},{"start": datetime(2020, 1, 2, 15, 30, 0), "end": datetime(2020, 1, 2, 17, 30, 0)}], -# "night": [{"start": datetime(2019, 12, 31, 17, 30, 0), "end": datetime(2020, 1, 1, 7, 30, 0)}, {"start": datetime(2020, 1, 1, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}]}, -# 'DE601': {"sunrise": [{"start": datetime(2020, 1, 1, 7, 45, 0), "end": datetime(2020, 1, 1, 9, 45, 0)}, {"start": datetime(2020, 1, 2, 7, 45, 0), "end": datetime(2020, 1, 2, 9, 45, 0)}], -# "day": [{"start": datetime(2020, 1, 1, 9, 45, 0), "end": datetime(2020, 1, 1, 15, 45, 0)}, {"start": datetime(2020, 1, 2, 9, 45, 0), "end": datetime(2020, 1, 2, 15, 45, 0)}], -# "sunset": [{"start": datetime(2020, 1, 1, 15, 45, 0), "end": datetime(2020, 1, 1, 17, 45, 0)},{"start": datetime(2020, 1, 2, 15, 45, 0), "end": datetime(2020, 1, 2, 17, 45, 0)}], -# "night": [{"start": datetime(2019, 12, 31, 17, 45, 0), "end": datetime(2020, 1, 1, 7, 45, 0)}, {"start": datetime(2020, 1, 1, 17, 45, 0), "end": datetime(2020, 1, 2, 7, 45, 0)}]}} -# -# -# # constraint checker requests lower and upper bound, so we need some variants for various cases -# self.sunrise_data_early_night_early_night = { -# 'CS001': {"sunrise": [{"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)}, {"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)}], -# "day": [{"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}, {"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}], -# "sunset": [{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)},{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)}], -# "night": [{"start": datetime(2019, 12, 31, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}, {"start": datetime(2019, 12, 31, 17, 30, 0), "end": datetime(2020, 1, 1, 7, 30, 0)}]}} -# -# self.sunrise_data_early_night_late_night = { -# 'CS001': {"sunrise": [{"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)}, {"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)}], -# "day": [{"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}, {"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}], -# "sunset": [{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)},{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)}], -# "night": [{"start": datetime(2019, 12, 31, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}, {"start": datetime(2020, 1, 1, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}]}} -# -# self.sunrise_data_late_night_late_night = { -# 'CS001': {"sunrise": [{"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)}, {"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)}], -# "day": [{"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}, {"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}], -# "sunset": [{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)},{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)}], -# "night": [{"start": datetime(2020, 1, 1, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}, {"start": datetime(2020, 1, 1, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}]}} -# -# self.sunrise_data_late_night_early_night_next_day = { -# 'CS001': {"sunrise": [{"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)}, {"start": datetime(2020, 1, 2, 7, 30, 0), "end": datetime(2020, 1, 2, 9, 30, 0)}], -# "day": [{"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}, {"start": datetime(2020, 1, 2, 9, 30, 0), "end": datetime(2020, 1, 2, 15, 30, 0)}], -# "sunset": [{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)},{"start": datetime(2020, 1, 2, 15, 30, 0), "end": datetime(2020, 1, 2, 17, 30, 0)}], -# "night": [{"start": datetime(2020, 1, 1, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}, {"start": datetime(2020, 1, 1, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}]}} -# -# self.sunrise_data_late_night_late_night_next_day = { -# 'CS001': {"sunrise": [{"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)}, {"start": datetime(2020, 1, 2, 7, 30, 0), "end": datetime(2020, 1, 2, 9, 30, 0)}], -# "day": [{"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}, {"start": datetime(2020, 1, 2, 9, 30, 0), "end": datetime(2020, 1, 2, 15, 30, 0)}], -# "sunset": [{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)},{"start": datetime(2020, 1, 2, 15, 30, 0), "end": datetime(2020, 1, 2, 17, 30, 0)}], -# "night": [{"start": datetime(2020, 1, 1, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}, {"start": datetime(2020, 1, 2, 17, 30, 0), "end": datetime(2020, 1, 3, 7, 30, 0)}]}} -# -# -# self.sunrise_patcher = mock.patch('lofar.sas.tmss.services.scheduling.constraints.template_constraints_v1.timestamps_and_stations_to_sun_rise_and_set') -# self.sunrise_mock = self.sunrise_patcher.start() -# self.sunrise_mock.return_value = self.sunrise_data -# self.addCleanup(self.sunrise_patcher.stop) -# -# # require_day -# -# def test_get_earliest_possible_start_time_with_daytime_constraint_returns_day_start(self): -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_day'] = True -# self.scheduling_unit_blueprint.save() -# self.sunrise_mock.return_value = self.sunrise_data_early_night -# timestamp = datetime(2020, 1, 1, 4, 0, 0) -# returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) -# self.assertEqual(returned_time, self.sunrise_data['CS001']['day'][0]['start']) -# -# def test_get_earliest_possible_start_time_with_daytime_constraint_returns_day_start_of_latest_station(self): -# self.scheduling_unit_blueprint.requirements_doc['tasks']['Observation']['specifications_doc']['station_groups'] = [{'stations': ['CS001', 'DE601']}] -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_day'] = True -# self.scheduling_unit_blueprint.save() -# self.sunrise_mock.return_value = self.sunrise_data_early_night -# timestamp = datetime(2020, 1, 1, 4, 0, 0) -# returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) -# self.assertEqual(returned_time, self.sunrise_data['DE601']['day'][0]['start']) -# -# def test_get_earliest_possible_start_time_with_daytime_constraint_returns_timestamp(self): -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_day'] = True -# self.scheduling_unit_blueprint.save() -# timestamp = datetime(2020, 1, 1, 10, 0, 0) -# returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) -# self.assertEqual(returned_time, timestamp) -# -# def test_get_earliest_possible_start_time_with_daytime_constraint_returns_next_day_start(self): -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_day'] = True -# self.scheduling_unit_blueprint.save() -# timestamp = datetime(2020, 1, 1, 20, 0, 0) -# returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) -# self.assertEqual(returned_time, self.sunrise_data['CS001']['day'][1]['start']) -# -# def test_get_earliest_possible_start_time_with_daytime_constraint_returns_next_day_start_when_obs_does_not_fit(self): -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_day'] = True -# self.scheduling_unit_blueprint.save() -# timestamp = datetime(2020, 1, 1, 14, 0, 0) -# returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) -# self.assertEqual(returned_time, self.sunrise_data['CS001']['day'][1]['start']) -# -# def test_can_run_anywhere_within_timewindow_with_daily_constraints_with_daytime_constraint_returns_true(self): -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_day'] = True -# self.scheduling_unit_blueprint.save() -# -# self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night -# lower_bound = datetime(2020, 1, 1, 10, 0, 0) -# upper_bound = datetime(2020, 1, 1, 15, 0, 0) -# self.assertTrue(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) -# -# def test_can_run_anywhere_within_timewindow_with_daily_constraints_with_daytime_constraint_returns_false_when_not_daytime(self): -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_day'] = True -# self.scheduling_unit_blueprint.save() -# -# self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night -# lower_bound = datetime(2020, 1, 1, 20, 0, 0) -# upper_bound = datetime(2020, 1, 1, 23, 0, 0) -# self.assertFalse(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) -# -# def test_can_run_anywhere_within_timewindow_with_daily_constraints_with_daytime_constraint_returns_false_when_partially_not_daytime(self): -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_day'] = True -# self.scheduling_unit_blueprint.save() -# -# self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night -# lower_bound = datetime(2020, 1, 1, 14, 0, 0) -# upper_bound = datetime(2020, 1, 1, 18, 0, 0) -# self.assertFalse(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) -# -# self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night -# lower_bound = datetime(2020, 1, 1, 8, 0, 0) -# upper_bound = datetime(2020, 1, 1, 12, 0, 0) -# self.assertFalse(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) -# -# def test_can_run_within_timewindow_with_daytime_constraint_returns_correct_value(self): -# # todo: for time ranges across dates, consider removing the mock for this because the moving window cannot be easily mocked -# # remove other constraints: -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky'] = {} -# -# # set constraint to test -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_day'] = True -# self.scheduling_unit_blueprint.save() -# -# # can run in day -# self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night -# lower_bound = datetime(2020, 1, 1, 8, 0, 0) -# upper_bound = datetime(2020, 1, 1, 15, 0, 0) -# self.assertTrue(can_run_within_timewindow(self.scheduling_unit_blueprint, lower_bound, upper_bound)) -# -# # cannot run at night -# self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night -# lower_bound = datetime(2020, 1, 1, 15, 0, 0) -# upper_bound = datetime(2020, 1, 1, 23, 0, 0) -# self.assertFalse(can_run_within_timewindow(self.scheduling_unit_blueprint, lower_bound, upper_bound)) -# -# # require_night -# -# def test_get_earliest_possible_start_time_with_nighttime_constraint_returns_night_start(self): -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_night'] = True -# self.scheduling_unit_blueprint.save() -# timestamp = datetime(2020, 1, 1, 14, 0, 0) -# returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) -# self.assertEqual(returned_time, self.sunrise_data['CS001']['night'][0]['start']) -# -# def test_get_earliest_possible_start_time_with_nighttime_constraint_returns_night_start_of_latest_station(self): -# self.scheduling_unit_blueprint.requirements_doc['tasks']['Observation']['specifications_doc']['station_groups'] = [{'stations': ['CS001', 'DE601']}] -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_night'] = True -# self.scheduling_unit_blueprint.save() -# timestamp = datetime(2020, 1, 1, 14, 0, 0) -# returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) -# self.assertEqual(returned_time, self.sunrise_data['DE601']['night'][0]['start']) -# -# def test_get_earliest_possible_start_time_with_nighttime_constraint_returns_timestamp(self): -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_night'] = True -# self.scheduling_unit_blueprint.save() -# -# # late night -# timestamp = datetime(2020, 1, 1, 23, 0, 0) -# returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) -# self.assertEqual(returned_time, timestamp) -# -# # early night -# self.sunrise_mock.return_value = self.sunrise_data_early_night -# timestamp = datetime(2020, 1, 1, 3, 0, 0) -# returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) -# self.assertEqual(returned_time, timestamp) -# -# def test_get_earliest_possible_start_time_with_nighttime_constraint_returns_next_night_start_when_obs_does_not_fit(self): -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_night'] = True -# self.scheduling_unit_blueprint.save() -# -# # early night -# self.sunrise_mock.return_value = self.sunrise_data_early_night -# timestamp = datetime(2020, 1, 1, 6, 0, 0) -# returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) -# self.assertEqual(returned_time, self.sunrise_data_early_night['CS001']['night'][1]['start']) -# -# def test_can_run_anywhere_within_timewindow_with_daily_constraints_with_nighttime_constraint_returns_true(self): -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_night'] = True -# self.scheduling_unit_blueprint.save() -# -# # early night -# self.sunrise_mock.return_value = self.sunrise_data_early_night_early_night -# lower_bound = datetime(2020, 1, 1, 1, 0, 0) -# upper_bound = datetime(2020, 1, 1, 3, 0, 0) -# self.assertTrue(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) -# -# # late night -# self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night -# lower_bound = datetime(2020, 1, 1, 20, 0, 0) -# upper_bound = datetime(2020, 1, 1, 23, 0, 0) -# self.assertTrue(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) -# -# # night-night next day -# self.sunrise_mock.return_value = self.sunrise_data_late_night_early_night_next_day -# lower_bound = datetime(2020, 1, 1, 23, 0, 0) -# upper_bound = datetime(2020, 1, 2, 3, 0, 0) -# self.assertTrue(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) -# -# def test_can_run_anywhere_within_timewindow_with_daily_constraints_with_nighttime_constraint_returns_false_when_not_nighttime(self): -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_night'] = True -# self.scheduling_unit_blueprint.save() -# -# self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night -# lower_bound = datetime(2020, 1, 1, 10, 0, 0) -# upper_bound = datetime(2020, 1, 1, 14, 0, 0) -# self.assertFalse(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) -# -# def test_can_run_anywhere_within_timewindow_with_daily_constraints_with_nighttime_constraint_returns_false_when_partially_not_nighttime(self): -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_night'] = True -# self.scheduling_unit_blueprint.save() -# -# # night-day next day -# self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night_next_day -# lower_bound = datetime(2020, 1, 1, 23, 0, 0) -# upper_bound = datetime(2020, 1, 2, 10, 0, 0) -# self.assertFalse(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) -# -# # day-night next day -# self.sunrise_mock.return_value = self.sunrise_data_late_night_early_night_next_day -# lower_bound = datetime(2020, 1, 1, 14, 0, 0) -# upper_bound = datetime(2020, 1, 2, 3, 0, 0) -# self.assertFalse(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) -# -# # day-night same day -# self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night -# lower_bound = datetime(2020, 1, 1, 14, 0, 0) -# upper_bound = datetime(2020, 1, 1, 20, 0, 0) -# self.assertFalse(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) -# -# # night-day same day -# self.sunrise_mock.return_value = self.sunrise_data_early_night_late_night -# lower_bound = datetime(2020, 1, 1, 3, 0, 0) -# upper_bound = datetime(2020, 1, 1, 10, 0, 0) -# self.assertFalse(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) -# -# # day-night-day -# self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night_next_day -# lower_bound = datetime(2020, 1, 1, 14, 0, 0) -# upper_bound = datetime(2020, 1, 2, 10, 0, 0) -# self.assertFalse(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) -# -# # night-day-night -# self.sunrise_mock.return_value = self.sunrise_data_early_night_late_night -# lower_bound = datetime(2020, 1, 1, 3, 0, 0) -# upper_bound = datetime(2020, 1, 1, 23, 0, 0) -# self.assertFalse(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) -# -# def test_can_run_within_timewindow_with_nighttime_constraint_returns_correct_value(self): -# # todo: for time ranges across dates, consider removing the mock for this because the moving window cannot be easily mocked -# # remove other constraints: -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky'] = {} -# -# # set constraint to test -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_night'] = True -# self.scheduling_unit_blueprint.save() -# -# # cannot run in day -# self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night -# lower_bound = datetime(2020, 1, 1, 8, 0, 0) -# upper_bound = datetime(2020, 1, 1, 15, 0, 0) -# self.assertFalse(can_run_within_timewindow(self.scheduling_unit_blueprint, lower_bound, upper_bound)) -# -# # can run at night -# self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night -# lower_bound = datetime(2020, 1, 1, 15, 0, 0) -# upper_bound = datetime(2020, 1, 1, 23, 0, 0) -# self.assertTrue(can_run_within_timewindow(self.scheduling_unit_blueprint, lower_bound, upper_bound)) -# -# -# # avoid_twilight -# -# def test_get_earliest_possible_start_time_with_twilight_constraint_returns_day_start(self): -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['avoid_twilight'] = True -# self.scheduling_unit_blueprint.save() -# -# self.sunrise_mock.return_value = self.sunrise_data_early_night -# timestamp = datetime(2020, 1, 1, 9, 0, 0) -# returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) -# self.assertEqual(returned_time, self.sunrise_data['CS001']['day'][0]['start']) -# -# def test_get_earliest_possible_start_time_with_twilight_constraint_returns_day_start_of_latest_station(self): -# self.scheduling_unit_blueprint.requirements_doc['tasks']['Observation']['specifications_doc']['station_groups'] = [{'stations': ['CS001', 'DE601']}] -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['avoid_twilight'] = True -# self.scheduling_unit_blueprint.save() -# -# self.sunrise_mock.return_value = self.sunrise_data_early_night -# timestamp = datetime(2020, 1, 1, 9, 0, 0) -# returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) -# self.assertEqual(returned_time, self.sunrise_data['DE601']['day'][0]['start']) -# -# def test_get_earliest_possible_start_time_with_twilight_constraint_returns_night_start(self): -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['avoid_twilight'] = True -# self.scheduling_unit_blueprint.save() -# -# self.sunrise_mock.return_value = self.sunrise_data -# timestamp = datetime(2020, 1, 1, 17, 0, 0) -# returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) -# self.assertEqual(returned_time, self.sunrise_data['CS001']['night'][0]['start']) -# -# def test_get_earliest_possible_start_time_with_twilight_constraint_returns_night_start_of_latest_station(self): -# self.scheduling_unit_blueprint.requirements_doc['tasks']['Observation']['specifications_doc']['station_groups'] = [{'stations': ['CS001', 'DE601']}] -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['avoid_twilight'] = True -# self.scheduling_unit_blueprint.save() -# -# self.sunrise_mock.return_value = self.sunrise_data -# timestamp = datetime(2020, 1, 1, 17, 0, 0) -# returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) -# self.assertEqual(returned_time, self.sunrise_data['DE601']['night'][0]['start']) -# -# def test_get_earliest_possible_start_time_with_twilight_constraint_returns_timestamp(self): -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['avoid_twilight'] = True -# self.scheduling_unit_blueprint.save() -# -# # daytime -# timestamp = datetime(2020, 1, 1, 10, 0, 0) -# returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) -# self.assertEqual(returned_time, timestamp) -# -# # late time -# timestamp = datetime(2020, 1, 1, 20, 0, 0) -# returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) -# self.assertEqual(returned_time, timestamp) -# -# # early night -# self.sunrise_mock.return_value = self.sunrise_data_early_night -# timestamp = datetime(2020, 1, 1, 3, 0, 0) -# returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) -# self.assertEqual(returned_time, timestamp) -# -# def test_get_earliest_possible_start_time_with_twilight_constraint_returns_day_or_night_start_when_obs_does_not_fit(self): -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['avoid_twilight'] = True -# self.scheduling_unit_blueprint.save() -# -# timestamp = datetime(2020, 1, 1, 15, 0, 0) -# returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) -# self.assertEqual(returned_time, self.sunrise_data['CS001']['night'][0]['start']) -# -# self.sunrise_mock.return_value = self.sunrise_data_early_night -# timestamp = datetime(2020, 1, 1, 7, 0, 0) -# returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) -# self.assertEqual(returned_time, self.sunrise_data['CS001']['day'][0]['start']) -# -# def test_can_run_anywhere_within_timewindow_with_daily_constraints_with_twilight_constraint_returns_true(self): -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['avoid_twilight'] = True -# self.scheduling_unit_blueprint.save() -# -# self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night -# lower_bound = datetime(2020, 1, 1, 10, 0, 0) -# upper_bound = datetime(2020, 1, 1, 15, 0, 0) -# self.assertTrue(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) -# -# def test_can_run_anywhere_within_timewindow_with_daily_constraints_with_twilight_constraint_returns_false_when_in_twilight(self): -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['avoid_twilight'] = True -# self.scheduling_unit_blueprint.save() -# -# self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night -# lower_bound = datetime(2020, 1, 1, 8, 0, 0) -# upper_bound = datetime(2020, 1, 1, 9, 0, 0) -# self.assertFalse(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) -# -# self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night -# lower_bound = datetime(2020, 1, 1, 16, 0, 0) -# upper_bound = datetime(2020, 1, 1, 17, 0, 0) -# self.assertFalse(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) -# -# def test_can_run_anywhere_within_timewindow_with_daily_constraints_with_twilight_constraint_returns_false_when_partially_in_twilight(self): -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['avoid_twilight'] = True -# self.scheduling_unit_blueprint.save() -# -# self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night -# lower_bound = datetime(2020, 1, 1, 10, 0, 0) -# upper_bound = datetime(2020, 1, 1, 18, 0, 0) -# self.assertFalse(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) -# -# self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night -# lower_bound = datetime(2020, 1, 1, 8, 0, 0) -# upper_bound = datetime(2020, 1, 1, 10, 0, 0) -# self.assertFalse(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) -# -# def test_can_run_within_timewindow_with_twilight_constraint_returns_correct_value(self): -# # todo: for time ranges across dates, consider removing the mock for this because the moving window cannot be easily mocked -# # remove other constraints: -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky'] = {} -# -# # set constraint to test -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['avoid_twilight'] = True -# self.scheduling_unit_blueprint.save() -# -# # can run in day -# self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night -# lower_bound = datetime(2020, 1, 1, 8, 0, 0) -# upper_bound = datetime(2020, 1, 1, 15, 0, 0) -# self.assertTrue(can_run_within_timewindow(self.scheduling_unit_blueprint, lower_bound, upper_bound)) -# -# # can run at night -# self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night -# lower_bound = datetime(2020, 1, 1, 15, 0, 0) -# upper_bound = datetime(2020, 1, 1, 23, 0, 0) -# self.assertTrue(can_run_within_timewindow(self.scheduling_unit_blueprint, lower_bound, upper_bound)) -# -# -# class TestSkyConstraints(unittest.TestCase): -# ''' -# Tests for the constraint checkers used in dynamic scheduling -# ''' -# -# def setUp(self) -> None: -# # scheduling unit -# self.obs_duration = 120 * 60 -# scheduling_set = models.SchedulingSet.objects.create(**SchedulingSet_test_data()) -# scheduling_unit_draft = TestDynamicScheduling.create_simple_observation_scheduling_unit("scheduling unit for ...%s" % self._testMethodName[30:], -# scheduling_set=scheduling_set, -# obs_duration=self.obs_duration) -# self.scheduling_unit_blueprint = create_task_blueprints_and_subtasks_from_scheduling_unit_draft(scheduling_unit_draft) -# -# # mock out conversions for speedup and assertable timestamps -# self.distance_data = { -# "sun": {datetime(2020, 1, 1, 10, 0, 0): Angle("0.3rad"), datetime(2020, 1, 1, 12, 0, 0): Angle("0.35rad")}, -# "moon": {datetime(2020, 1, 1, 10, 0, 0): Angle("0.2rad"), datetime(2020, 1, 1, 12, 0, 0): Angle("0.25rad")}, -# "jupiter": {datetime(2020, 1, 1, 10, 0, 0): Angle("0.1rad"), datetime(2020, 1, 1, 12, 0, 0): Angle("0.15rad")} -# } -# self.distance_patcher = mock.patch('lofar.sas.tmss.services.scheduling.constraints.template_constraints_v1.coordinates_and_timestamps_to_separation_from_bodies') -# self.distance_mock = self.distance_patcher.start() -# self.distance_mock.return_value = self.distance_data -# self.addCleanup(self.distance_patcher.stop) -# -# self.target_rise_and_set_data = {"CS002": [{"rise": datetime(2020, 1, 1, 8, 0, 0), "set": datetime(2020, 1, 1, 12, 30, 0)}, -# {"rise": datetime(2020, 1, 1, 8, 0, 0), "set": datetime(2020, 1, 1, 12, 30, 0)}]} -# self.target_rise_and_set_patcher = mock.patch('lofar.sas.tmss.services.scheduling.constraints.template_constraints_v1.coordinates_timestamps_and_stations_to_target_rise_and_set') -# self.target_rise_and_set_mock = self.target_rise_and_set_patcher.start() -# self.target_rise_and_set_mock.return_value = self.target_rise_and_set_data -# self.addCleanup(self.target_rise_and_set_patcher.stop) -# -# # min_distance -# -# def test_can_run_anywhere_within_timewindow_with_sky_constraints_with_min_distance_constraint_returns_true_when_met(self): -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky'] = {'min_distance': {'sun': 0.1, 'moon': 0.1, 'jupiter': 0.1}} -# self.scheduling_unit_blueprint.save() -# timestamp = datetime(2020, 1, 1, 10, 0, 0) -# returned_value = tc1.can_run_anywhere_within_timewindow_with_sky_constraints(self.scheduling_unit_blueprint, timestamp, timestamp + timedelta(seconds=self.obs_duration)) -# self.assertTrue(returned_value) -# -# def test_can_run_anywhere_within_timewindow_with_sky_constraints_with_min_distance_constraint_returns_false_when_not_met(self): -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky'] = {'min_distance': {'sun': 0.2, 'moon': 0.2, 'jupiter': 0.2}} -# self.scheduling_unit_blueprint.save() -# timestamp = datetime(2020, 1, 1, 10, 0, 0) -# returned_value = tc1.can_run_anywhere_within_timewindow_with_sky_constraints(self.scheduling_unit_blueprint, timestamp, timestamp + timedelta(seconds=self.obs_duration)) -# self.assertFalse(returned_value) -# -# # min_target_elevation -# -# def test_can_run_anywhere_within_timewindow_with_sky_constraints_with_min_target_elevation_constraint_returns_true_when_met(self): -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky'] = {'min_target_elevation': 0.1} -# self.scheduling_unit_blueprint.save() -# timestamp = datetime(2020, 1, 1, 10, 0, 0) -# returned_value = tc1.can_run_anywhere_within_timewindow_with_sky_constraints(self.scheduling_unit_blueprint, timestamp, timestamp + timedelta(seconds=self.obs_duration)) -# self.assertTrue(returned_value) -# -# def test_can_run_anywhere_within_timewindow_with_sky_constraints_with_min_target_elevation_constraint_returns_false_when_not_met(self): -# self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky'] = {'min_target_elevation': 0.2} -# self.scheduling_unit_blueprint.save() -# timestamp = datetime(2020, 1, 1, 11, 0, 0) -# returned_value = tc1.can_run_anywhere_within_timewindow_with_sky_constraints(self.scheduling_unit_blueprint, timestamp, timestamp + timedelta(seconds=self.obs_duration)) -# self.assertFalse(returned_value) + @unittest.skip("FIX TEST, skipping it for now, see TODO comment in assign_start_stop_times_to_schedulable_scheduling_units") + def test_three_simple_observations_no_constraints_different_project_priority(self): + scheduling_unit_draft_low = self.create_simple_observation_scheduling_unit("scheduling unit low", scheduling_set=self.scheduling_set_low) + scheduling_unit_blueprint_low = create_task_blueprints_and_subtasks_from_scheduling_unit_draft(scheduling_unit_draft_low) + + scheduling_unit_draft_medium = self.create_simple_observation_scheduling_unit("scheduling unit medium", scheduling_set=self.scheduling_set_medium) + scheduling_unit_blueprint_medium = create_task_blueprints_and_subtasks_from_scheduling_unit_draft(scheduling_unit_draft_medium) + + scheduling_unit_draft_high = self.create_simple_observation_scheduling_unit("scheduling unit high", scheduling_set=self.scheduling_set_high) + scheduling_unit_blueprint_high = create_task_blueprints_and_subtasks_from_scheduling_unit_draft(scheduling_unit_draft_high) + + # call the method-under-test. + scheduled_scheduling_unit = do_dynamic_schedule() + + # we expect the scheduling_unit with the highest project rank to be scheduled first + self.assertIsNotNone(scheduled_scheduling_unit) + self.assertEqual(scheduling_unit_blueprint_high.id, scheduled_scheduling_unit.id) + + # check the results + # we expect the sub_high to be scheduled + scheduling_unit_blueprint_low.refresh_from_db() + scheduling_unit_blueprint_medium.refresh_from_db() + scheduling_unit_blueprint_high.refresh_from_db() + self.assertEqual(scheduling_unit_blueprint_low.status, 'schedulable') + self.assertEqual(scheduling_unit_blueprint_medium.status, 'schedulable') + self.assertEqual(scheduling_unit_blueprint_high.status, 'scheduled') + + # check the scheduled subtask + upcoming_scheduled_subtasks = models.Subtask.objects.filter(state__value='scheduled', + task_blueprint__scheduling_unit_blueprint__in=(scheduling_unit_blueprint_low, + scheduling_unit_blueprint_medium, + scheduling_unit_blueprint_high)).all() + self.assertEqual(1, upcoming_scheduled_subtasks.count()) + self.assertEqual(scheduling_unit_blueprint_high.id, upcoming_scheduled_subtasks[0].task_blueprint.scheduling_unit_blueprint.id) + + # check scheduling_unit_blueprint_low starts after the scheduled scheduling_unit_blueprint_high + self.assertGreater(scheduling_unit_blueprint_low.start_time, scheduling_unit_blueprint_medium.start_time) + self.assertGreater(scheduling_unit_blueprint_medium.start_time, scheduling_unit_blueprint_high.start_time) + + # ensure DEFAULT_INTER_OBSERVATION_GAP between them + self.assertGreaterEqual(scheduling_unit_blueprint_medium.start_time - scheduling_unit_blueprint_high.stop_time, DEFAULT_INTER_OBSERVATION_GAP) + self.assertGreaterEqual(scheduling_unit_blueprint_low.start_time - scheduling_unit_blueprint_medium.stop_time, DEFAULT_INTER_OBSERVATION_GAP) + + def test_time_bound_unit_wins_even_at_lower_priority(self): + # create two schedule units, one with high one with low prio. + # first create them without any further constraints, and check if high prio wins. + scheduling_unit_draft_low = self.create_simple_observation_scheduling_unit("scheduling unit low", scheduling_set=self.scheduling_set_low) + scheduling_unit_blueprint_low = create_task_blueprints_and_subtasks_from_scheduling_unit_draft(scheduling_unit_draft_low) + + scheduling_unit_draft_high = self.create_simple_observation_scheduling_unit("scheduling unit high", scheduling_set=self.scheduling_set_high) + scheduling_unit_blueprint_high = create_task_blueprints_and_subtasks_from_scheduling_unit_draft(scheduling_unit_draft_high) + + now = datetime.utcnow() + tomorrow = now+timedelta(days=1) + + # call the method-under-test. + best_scored_scheduling_unit = find_best_next_schedulable_unit([scheduling_unit_blueprint_low, scheduling_unit_blueprint_high], now, tomorrow) + + # we expect the scheduling_unit with the highest project rank to be scheduled first + self.assertEqual(scheduling_unit_blueprint_high.id, best_scored_scheduling_unit.scheduling_unit.id) + + #now update the low prio unit with a time constraint, "forcing" it to be run in a very thight upcoming time window. + scheduling_unit_draft_low.scheduling_constraints_doc['time'] = { 'before': (now+scheduling_unit_draft_low.duration).isoformat()+'Z' } + scheduling_unit_draft_low.save() + scheduling_unit_blueprint_low.refresh_from_db() + + # call the method-under-test. + best_scored_scheduling_unit = find_best_next_schedulable_unit([scheduling_unit_blueprint_low, scheduling_unit_blueprint_high], now, tomorrow) + + # now we expect the scheduling_unit with the lowest project rank to be scheduled first because it can only run within this limited timewindow + self.assertEqual(scheduling_unit_draft_low.id, best_scored_scheduling_unit.scheduling_unit.id) + + + # update the low prio unit. enlarge the time window constraint a bit, so both low and high prio units can fit + # this should result that the high prio goes first, and the low prio (which now fits as well) goes second + scheduling_unit_draft_low.scheduling_constraints_doc['time'] = \ + { 'before': (now+scheduling_unit_draft_low.duration+scheduling_unit_draft_high.duration).isoformat()+'Z' } + scheduling_unit_draft_low.save() + scheduling_unit_blueprint_low.refresh_from_db() + + # call the method-under-test. + best_scored_scheduling_unit = find_best_next_schedulable_unit([scheduling_unit_blueprint_low, scheduling_unit_blueprint_high], now, tomorrow) + + # now we expect the scheduling_unit with the lowest project rank to be scheduled first because it can only + # run within this limited timewindow + self.assertEqual(scheduling_unit_blueprint_low.id, best_scored_scheduling_unit.scheduling_unit.id) + + # call the method-under-test again but search after first unit (should return low prio unit) + stop_time_of_first = best_scored_scheduling_unit.start_time + best_scored_scheduling_unit.scheduling_unit.duration + best_scored_scheduling_unit = find_best_next_schedulable_unit([scheduling_unit_blueprint_low, scheduling_unit_blueprint_high], stop_time_of_first, tomorrow) + self.assertEqual(scheduling_unit_blueprint_low.id, best_scored_scheduling_unit.scheduling_unit.id) + + def test_manual_constraint_is_preventing_scheduling_unit_from_being_scheduled_dynamically(self): + scheduling_unit_draft_manual = self.create_simple_observation_scheduling_unit("scheduling unit manual low", scheduling_set=self.scheduling_set_low, + constraints={'scheduler': 'manual'}) + scheduling_unit_blueprint_manual = create_task_blueprints_and_subtasks_from_scheduling_unit_draft(scheduling_unit_draft_manual) + self.assertEqual(scheduling_unit_blueprint_manual.status, "schedulable") + + # call the method-under-test. + scheduled_scheduling_unit = do_dynamic_schedule() + + # we expect no scheduling_unit to be scheduled, because the only one is set to 'manual' constraint + self.assertIsNone(scheduled_scheduling_unit) + + # check the results + scheduling_unit_blueprint_manual.refresh_from_db() + self.assertEqual(scheduling_unit_blueprint_manual.status, 'schedulable') + + @unittest.skip("FIX TEST, skipping it for now,...something with manual scheduler ?") + def test_manually_scheduled_blocking_dynamically_scheduled(self): + scheduling_unit_draft_manual = self.create_simple_observation_scheduling_unit("scheduling unit manual low", scheduling_set=self.scheduling_set_low, + constraints={'scheduler': 'manual'}) + scheduling_unit_blueprint_manual = create_task_blueprints_and_subtasks_from_scheduling_unit_draft(scheduling_unit_draft_manual) + self.assertEqual(scheduling_unit_blueprint_manual.status, "schedulable") + + schedule_independent_subtasks_in_scheduling_unit_blueprint(scheduling_unit_blueprint_manual, datetime.utcnow()) + self.assertEqual(scheduling_unit_blueprint_manual.status, "scheduled") + + scheduling_unit_draft_high = self.create_simple_observation_scheduling_unit("scheduling unit online high", scheduling_set=self.scheduling_set_high) + scheduling_unit_blueprint_high = create_task_blueprints_and_subtasks_from_scheduling_unit_draft(scheduling_unit_draft_high) + + # call the method-under-test. + scheduled_scheduling_unit = do_dynamic_schedule() + + # we expect the no scheduling_unit to be scheduled, because the manual is in the way -> Fix it + self.assertIsNone(scheduled_scheduling_unit) + + # check the results + # we expect the sub_high to be scheduled + scheduling_unit_blueprint_high.refresh_from_db() + self.assertEqual(scheduling_unit_blueprint_high.status, 'schedulable') + + # check scheduling_unit_blueprint_low starts after the scheduled scheduling_unit_blueprint_high + self.assertGreater(scheduling_unit_blueprint_high.start_time, scheduling_unit_blueprint_manual.start_time) + + # ensure DEFAULT_INTER_OBSERVATION_GAP between them + self.assertGreaterEqual(scheduling_unit_blueprint_high.start_time - scheduling_unit_blueprint_manual.stop_time, DEFAULT_INTER_OBSERVATION_GAP) + + +class TestDailyConstraints(TestCase): + ''' + Tests for the constraint checkers used in dynamic scheduling + ''' + + def setUp(self) -> None: + # scheduling unit + self.obs_duration = 120 * 60 + scheduling_set = models.SchedulingSet.objects.create(**SchedulingSet_test_data()) + scheduling_unit_draft = TestDynamicScheduling.create_simple_observation_scheduling_unit("scheduling unit for ...%s" % self._testMethodName[30:], + scheduling_set=scheduling_set, + obs_duration=self.obs_duration) + self.scheduling_unit_blueprint = create_task_blueprints_and_subtasks_from_scheduling_unit_draft(scheduling_unit_draft) + + # mock out conversions for speedup and assertable timestamps + # earliest_start_time requests timestamp and timestamp+1day + self.sunrise_data = { + 'CS001': {"sunrise": [{"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)}, {"start": datetime(2020, 1, 2, 7, 30, 0), "end": datetime(2020, 1, 2, 9, 30, 0)}], + "day": [{"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}, {"start": datetime(2020, 1, 2, 9, 30, 0), "end": datetime(2020, 1, 2, 15, 30, 0)}], + "sunset": [{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)},{"start": datetime(2020, 1, 2, 15, 30, 0), "end": datetime(2020, 1, 2, 17, 30, 0)}], + "night": [{"start": datetime(2020, 1, 1, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}, {"start": datetime(2020, 1, 2, 17, 30, 0), "end": datetime(2020, 1, 3, 7, 30, 0)}]}, + 'DE601': {"sunrise": [{"start": datetime(2020, 1, 1, 7, 45, 0), "end": datetime(2020, 1, 1, 9, 45, 0)}, {"start": datetime(2020, 1, 2, 7, 45, 0), "end": datetime(2020, 1, 2, 9, 45, 0)}], + "day": [{"start": datetime(2020, 1, 1, 9, 45, 0), "end": datetime(2020, 1, 1, 15, 45, 0)}, {"start": datetime(2020, 1, 2, 9, 45, 0), "end": datetime(2020, 1, 2, 15, 45, 0)}], + "sunset": [{"start": datetime(2020, 1, 1, 15, 45, 0), "end": datetime(2020, 1, 1, 17, 45, 0)}, {"start": datetime(2020, 1, 2, 15, 45, 0), "end": datetime(2020, 1, 2, 17, 45, 0)}], + "night": [{"start": datetime(2020, 1, 1, 17, 45, 0), "end": datetime(2020, 1, 2, 7, 45, 0)}, {"start": datetime(2020, 1, 2, 17, 45, 0), "end": datetime(2020, 1, 3, 7, 45, 0)}]}} + + # variant for timestamp before sunrise, which returns the previous night + self.sunrise_data_early_night = { + 'CS001': {"sunrise": [{"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)}, {"start": datetime(2020, 1, 2, 7, 30, 0), "end": datetime(2020, 1, 2, 9, 30, 0)}], + "day": [{"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}, {"start": datetime(2020, 1, 2, 9, 30, 0), "end": datetime(2020, 1, 2, 15, 30, 0)}], + "sunset": [{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)},{"start": datetime(2020, 1, 2, 15, 30, 0), "end": datetime(2020, 1, 2, 17, 30, 0)}], + "night": [{"start": datetime(2019, 12, 31, 17, 30, 0), "end": datetime(2020, 1, 1, 7, 30, 0)}, {"start": datetime(2020, 1, 1, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}]}, + 'DE601': {"sunrise": [{"start": datetime(2020, 1, 1, 7, 45, 0), "end": datetime(2020, 1, 1, 9, 45, 0)}, {"start": datetime(2020, 1, 2, 7, 45, 0), "end": datetime(2020, 1, 2, 9, 45, 0)}], + "day": [{"start": datetime(2020, 1, 1, 9, 45, 0), "end": datetime(2020, 1, 1, 15, 45, 0)}, {"start": datetime(2020, 1, 2, 9, 45, 0), "end": datetime(2020, 1, 2, 15, 45, 0)}], + "sunset": [{"start": datetime(2020, 1, 1, 15, 45, 0), "end": datetime(2020, 1, 1, 17, 45, 0)},{"start": datetime(2020, 1, 2, 15, 45, 0), "end": datetime(2020, 1, 2, 17, 45, 0)}], + "night": [{"start": datetime(2019, 12, 31, 17, 45, 0), "end": datetime(2020, 1, 1, 7, 45, 0)}, {"start": datetime(2020, 1, 1, 17, 45, 0), "end": datetime(2020, 1, 2, 7, 45, 0)}]}} + + + # constraint checker requests lower and upper bound, so we need some variants for various cases + self.sunrise_data_early_night_early_night = { + 'CS001': {"sunrise": [{"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)}, {"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)}], + "day": [{"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}, {"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}], + "sunset": [{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)},{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)}], + "night": [{"start": datetime(2019, 12, 31, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}, {"start": datetime(2019, 12, 31, 17, 30, 0), "end": datetime(2020, 1, 1, 7, 30, 0)}]}} + + self.sunrise_data_early_night_late_night = { + 'CS001': {"sunrise": [{"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)}, {"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)}], + "day": [{"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}, {"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}], + "sunset": [{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)},{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)}], + "night": [{"start": datetime(2019, 12, 31, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}, {"start": datetime(2020, 1, 1, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}]}} + + self.sunrise_data_late_night_late_night = { + 'CS001': {"sunrise": [{"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)}, {"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)}], + "day": [{"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}, {"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}], + "sunset": [{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)},{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)}], + "night": [{"start": datetime(2020, 1, 1, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}, {"start": datetime(2020, 1, 1, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}]}} + + self.sunrise_data_late_night_early_night_next_day = { + 'CS001': {"sunrise": [{"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)}, {"start": datetime(2020, 1, 2, 7, 30, 0), "end": datetime(2020, 1, 2, 9, 30, 0)}], + "day": [{"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}, {"start": datetime(2020, 1, 2, 9, 30, 0), "end": datetime(2020, 1, 2, 15, 30, 0)}], + "sunset": [{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)},{"start": datetime(2020, 1, 2, 15, 30, 0), "end": datetime(2020, 1, 2, 17, 30, 0)}], + "night": [{"start": datetime(2020, 1, 1, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}, {"start": datetime(2020, 1, 1, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}]}} + + self.sunrise_data_late_night_late_night_next_day = { + 'CS001': {"sunrise": [{"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)}, {"start": datetime(2020, 1, 2, 7, 30, 0), "end": datetime(2020, 1, 2, 9, 30, 0)}], + "day": [{"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}, {"start": datetime(2020, 1, 2, 9, 30, 0), "end": datetime(2020, 1, 2, 15, 30, 0)}], + "sunset": [{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)},{"start": datetime(2020, 1, 2, 15, 30, 0), "end": datetime(2020, 1, 2, 17, 30, 0)}], + "night": [{"start": datetime(2020, 1, 1, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}, {"start": datetime(2020, 1, 2, 17, 30, 0), "end": datetime(2020, 1, 3, 7, 30, 0)}]}} + + + self.sunrise_patcher = mock.patch('lofar.sas.tmss.services.scheduling.constraints.template_constraints_v1.timestamps_and_stations_to_sun_rise_and_set') + self.sunrise_mock = self.sunrise_patcher.start() + self.sunrise_mock.return_value = self.sunrise_data + self.addCleanup(self.sunrise_patcher.stop) + + # require_day + + def test_get_earliest_possible_start_time_with_daytime_constraint_returns_day_start(self): + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_day'] = True + self.scheduling_unit_blueprint.save() + self.sunrise_mock.return_value = self.sunrise_data_early_night + timestamp = datetime(2020, 1, 1, 4, 0, 0) + returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) + self.assertEqual(returned_time, self.sunrise_data['CS001']['day'][0]['start']) + + def test_get_earliest_possible_start_time_with_daytime_constraint_returns_day_start_of_latest_station(self): + self.scheduling_unit_blueprint.requirements_doc['tasks']['Observation']['specifications_doc']['station_groups'] = [{'stations': ['CS001', 'DE601']}] + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_day'] = True + self.scheduling_unit_blueprint.save() + self.sunrise_mock.return_value = self.sunrise_data_early_night + timestamp = datetime(2020, 1, 1, 4, 0, 0) + returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) + self.assertEqual(returned_time, self.sunrise_data['DE601']['day'][0]['start']) + + def test_get_earliest_possible_start_time_with_daytime_constraint_returns_timestamp(self): + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_day'] = True + self.scheduling_unit_blueprint.save() + timestamp = datetime(2020, 1, 1, 10, 0, 0) + returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) + self.assertEqual(returned_time, timestamp) + + def test_get_earliest_possible_start_time_with_daytime_constraint_returns_next_day_start(self): + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_day'] = True + self.scheduling_unit_blueprint.save() + timestamp = datetime(2020, 1, 1, 20, 0, 0) + returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) + self.assertEqual(returned_time, self.sunrise_data['CS001']['day'][1]['start']) + + def test_get_earliest_possible_start_time_with_daytime_constraint_returns_next_day_start_when_obs_does_not_fit(self): + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_day'] = True + self.scheduling_unit_blueprint.save() + timestamp = datetime(2020, 1, 1, 14, 0, 0) + returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) + self.assertEqual(returned_time, self.sunrise_data['CS001']['day'][1]['start']) + + def test_can_run_anywhere_within_timewindow_with_daily_constraints_with_daytime_constraint_returns_true(self): + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_day'] = True + self.scheduling_unit_blueprint.save() + + self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night + lower_bound = datetime(2020, 1, 1, 10, 0, 0) + upper_bound = datetime(2020, 1, 1, 15, 0, 0) + self.assertTrue(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) + + def test_can_run_anywhere_within_timewindow_with_daily_constraints_with_daytime_constraint_returns_false_when_not_daytime(self): + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_day'] = True + self.scheduling_unit_blueprint.save() + + self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night + lower_bound = datetime(2020, 1, 1, 20, 0, 0) + upper_bound = datetime(2020, 1, 1, 23, 0, 0) + self.assertFalse(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) + + def test_can_run_anywhere_within_timewindow_with_daily_constraints_with_daytime_constraint_returns_false_when_partially_not_daytime(self): + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_day'] = True + self.scheduling_unit_blueprint.save() + + self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night + lower_bound = datetime(2020, 1, 1, 14, 0, 0) + upper_bound = datetime(2020, 1, 1, 18, 0, 0) + self.assertFalse(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) + + self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night + lower_bound = datetime(2020, 1, 1, 8, 0, 0) + upper_bound = datetime(2020, 1, 1, 12, 0, 0) + self.assertFalse(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) + + def test_can_run_within_timewindow_with_daytime_constraint_returns_correct_value(self): + # todo: for time ranges across dates, consider removing the mock for this because the moving window cannot be easily mocked + # remove other constraints: + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky'] = {} + + # set constraint to test + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_day'] = True + self.scheduling_unit_blueprint.save() + + # can run in day + self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night + lower_bound = datetime(2020, 1, 1, 8, 0, 0) + upper_bound = datetime(2020, 1, 1, 15, 0, 0) + self.assertTrue(can_run_within_timewindow(self.scheduling_unit_blueprint, lower_bound, upper_bound)) + + # cannot run at night + self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night + lower_bound = datetime(2020, 1, 1, 15, 0, 0) + upper_bound = datetime(2020, 1, 1, 23, 0, 0) + self.assertFalse(can_run_within_timewindow(self.scheduling_unit_blueprint, lower_bound, upper_bound)) + + # require_night + + def test_get_earliest_possible_start_time_with_nighttime_constraint_returns_night_start(self): + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_night'] = True + self.scheduling_unit_blueprint.save() + timestamp = datetime(2020, 1, 1, 14, 0, 0) + returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) + self.assertEqual(returned_time, self.sunrise_data['CS001']['night'][0]['start']) + + def test_get_earliest_possible_start_time_with_nighttime_constraint_returns_night_start_of_latest_station(self): + self.scheduling_unit_blueprint.requirements_doc['tasks']['Observation']['specifications_doc']['station_groups'] = [{'stations': ['CS001', 'DE601']}] + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_night'] = True + self.scheduling_unit_blueprint.save() + timestamp = datetime(2020, 1, 1, 14, 0, 0) + returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) + self.assertEqual(returned_time, self.sunrise_data['DE601']['night'][0]['start']) + + def test_get_earliest_possible_start_time_with_nighttime_constraint_returns_timestamp(self): + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_night'] = True + self.scheduling_unit_blueprint.save() + + # late night + timestamp = datetime(2020, 1, 1, 23, 0, 0) + returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) + self.assertEqual(returned_time, timestamp) + + # early night + self.sunrise_mock.return_value = self.sunrise_data_early_night + timestamp = datetime(2020, 1, 1, 3, 0, 0) + returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) + self.assertEqual(returned_time, timestamp) + + def test_get_earliest_possible_start_time_with_nighttime_constraint_returns_next_night_start_when_obs_does_not_fit(self): + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_night'] = True + self.scheduling_unit_blueprint.save() + + # early night + self.sunrise_mock.return_value = self.sunrise_data_early_night + timestamp = datetime(2020, 1, 1, 6, 0, 0) + returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) + self.assertEqual(returned_time, self.sunrise_data_early_night['CS001']['night'][1]['start']) + + def test_can_run_anywhere_within_timewindow_with_daily_constraints_with_nighttime_constraint_returns_true(self): + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_night'] = True + self.scheduling_unit_blueprint.save() + + # early night + self.sunrise_mock.return_value = self.sunrise_data_early_night_early_night + lower_bound = datetime(2020, 1, 1, 1, 0, 0) + upper_bound = datetime(2020, 1, 1, 3, 0, 0) + self.assertTrue(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) + + # late night + self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night + lower_bound = datetime(2020, 1, 1, 20, 0, 0) + upper_bound = datetime(2020, 1, 1, 23, 0, 0) + self.assertTrue(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) + + # night-night next day + self.sunrise_mock.return_value = self.sunrise_data_late_night_early_night_next_day + lower_bound = datetime(2020, 1, 1, 23, 0, 0) + upper_bound = datetime(2020, 1, 2, 3, 0, 0) + self.assertTrue(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) + + def test_can_run_anywhere_within_timewindow_with_daily_constraints_with_nighttime_constraint_returns_false_when_not_nighttime(self): + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_night'] = True + self.scheduling_unit_blueprint.save() + + self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night + lower_bound = datetime(2020, 1, 1, 10, 0, 0) + upper_bound = datetime(2020, 1, 1, 14, 0, 0) + self.assertFalse(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) + + def test_can_run_anywhere_within_timewindow_with_daily_constraints_with_nighttime_constraint_returns_false_when_partially_not_nighttime(self): + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_night'] = True + self.scheduling_unit_blueprint.save() + + # night-day next day + self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night_next_day + lower_bound = datetime(2020, 1, 1, 23, 0, 0) + upper_bound = datetime(2020, 1, 2, 10, 0, 0) + self.assertFalse(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) + + # day-night next day + self.sunrise_mock.return_value = self.sunrise_data_late_night_early_night_next_day + lower_bound = datetime(2020, 1, 1, 14, 0, 0) + upper_bound = datetime(2020, 1, 2, 3, 0, 0) + self.assertFalse(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) + + # day-night same day + self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night + lower_bound = datetime(2020, 1, 1, 14, 0, 0) + upper_bound = datetime(2020, 1, 1, 20, 0, 0) + self.assertFalse(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) + + # night-day same day + self.sunrise_mock.return_value = self.sunrise_data_early_night_late_night + lower_bound = datetime(2020, 1, 1, 3, 0, 0) + upper_bound = datetime(2020, 1, 1, 10, 0, 0) + self.assertFalse(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) + + # day-night-day + self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night_next_day + lower_bound = datetime(2020, 1, 1, 14, 0, 0) + upper_bound = datetime(2020, 1, 2, 10, 0, 0) + self.assertFalse(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) + + # night-day-night + self.sunrise_mock.return_value = self.sunrise_data_early_night_late_night + lower_bound = datetime(2020, 1, 1, 3, 0, 0) + upper_bound = datetime(2020, 1, 1, 23, 0, 0) + self.assertFalse(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) + + def test_can_run_within_timewindow_with_nighttime_constraint_returns_correct_value(self): + # todo: for time ranges across dates, consider removing the mock for this because the moving window cannot be easily mocked + # remove other constraints: + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky'] = {} + + # set constraint to test + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_night'] = True + self.scheduling_unit_blueprint.save() + + # cannot run in day + self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night + lower_bound = datetime(2020, 1, 1, 8, 0, 0) + upper_bound = datetime(2020, 1, 1, 15, 0, 0) + self.assertFalse(can_run_within_timewindow(self.scheduling_unit_blueprint, lower_bound, upper_bound)) + + # can run at night + self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night + lower_bound = datetime(2020, 1, 1, 15, 0, 0) + upper_bound = datetime(2020, 1, 1, 23, 0, 0) + self.assertTrue(can_run_within_timewindow(self.scheduling_unit_blueprint, lower_bound, upper_bound)) + + + # avoid_twilight + + def test_get_earliest_possible_start_time_with_twilight_constraint_returns_day_start(self): + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['avoid_twilight'] = True + self.scheduling_unit_blueprint.save() + + self.sunrise_mock.return_value = self.sunrise_data_early_night + timestamp = datetime(2020, 1, 1, 9, 0, 0) + returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) + self.assertEqual(returned_time, self.sunrise_data['CS001']['day'][0]['start']) + + def test_get_earliest_possible_start_time_with_twilight_constraint_returns_day_start_of_latest_station(self): + self.scheduling_unit_blueprint.requirements_doc['tasks']['Observation']['specifications_doc']['station_groups'] = [{'stations': ['CS001', 'DE601']}] + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['avoid_twilight'] = True + self.scheduling_unit_blueprint.save() + + self.sunrise_mock.return_value = self.sunrise_data_early_night + timestamp = datetime(2020, 1, 1, 9, 0, 0) + returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) + self.assertEqual(returned_time, self.sunrise_data['DE601']['day'][0]['start']) + + def test_get_earliest_possible_start_time_with_twilight_constraint_returns_night_start(self): + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['avoid_twilight'] = True + self.scheduling_unit_blueprint.save() + + self.sunrise_mock.return_value = self.sunrise_data + timestamp = datetime(2020, 1, 1, 17, 0, 0) + returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) + self.assertEqual(returned_time, self.sunrise_data['CS001']['night'][0]['start']) + + def test_get_earliest_possible_start_time_with_twilight_constraint_returns_night_start_of_latest_station(self): + self.scheduling_unit_blueprint.requirements_doc['tasks']['Observation']['specifications_doc']['station_groups'] = [{'stations': ['CS001', 'DE601']}] + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['avoid_twilight'] = True + self.scheduling_unit_blueprint.save() + + self.sunrise_mock.return_value = self.sunrise_data + timestamp = datetime(2020, 1, 1, 17, 0, 0) + returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) + self.assertEqual(returned_time, self.sunrise_data['DE601']['night'][0]['start']) + + def test_get_earliest_possible_start_time_with_twilight_constraint_returns_timestamp(self): + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['avoid_twilight'] = True + self.scheduling_unit_blueprint.save() + + # daytime + timestamp = datetime(2020, 1, 1, 10, 0, 0) + returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) + self.assertEqual(returned_time, timestamp) + + # late time + timestamp = datetime(2020, 1, 1, 20, 0, 0) + returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) + self.assertEqual(returned_time, timestamp) + + # early night + self.sunrise_mock.return_value = self.sunrise_data_early_night + timestamp = datetime(2020, 1, 1, 3, 0, 0) + returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) + self.assertEqual(returned_time, timestamp) + + def test_get_earliest_possible_start_time_with_twilight_constraint_returns_day_or_night_start_when_obs_does_not_fit(self): + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['avoid_twilight'] = True + self.scheduling_unit_blueprint.save() + + timestamp = datetime(2020, 1, 1, 15, 0, 0) + returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) + self.assertEqual(returned_time, self.sunrise_data['CS001']['night'][0]['start']) + + self.sunrise_mock.return_value = self.sunrise_data_early_night + timestamp = datetime(2020, 1, 1, 7, 0, 0) + returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp) + self.assertEqual(returned_time, self.sunrise_data['CS001']['day'][0]['start']) + + def test_can_run_anywhere_within_timewindow_with_daily_constraints_with_twilight_constraint_returns_true(self): + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['avoid_twilight'] = True + self.scheduling_unit_blueprint.save() + + self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night + lower_bound = datetime(2020, 1, 1, 10, 0, 0) + upper_bound = datetime(2020, 1, 1, 15, 0, 0) + self.assertTrue(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) + + def test_can_run_anywhere_within_timewindow_with_daily_constraints_with_twilight_constraint_returns_false_when_in_twilight(self): + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['avoid_twilight'] = True + self.scheduling_unit_blueprint.save() + + self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night + lower_bound = datetime(2020, 1, 1, 8, 0, 0) + upper_bound = datetime(2020, 1, 1, 9, 0, 0) + self.assertFalse(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) + + self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night + lower_bound = datetime(2020, 1, 1, 16, 0, 0) + upper_bound = datetime(2020, 1, 1, 17, 0, 0) + self.assertFalse(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) + + def test_can_run_anywhere_within_timewindow_with_daily_constraints_with_twilight_constraint_returns_false_when_partially_in_twilight(self): + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['avoid_twilight'] = True + self.scheduling_unit_blueprint.save() + + self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night + lower_bound = datetime(2020, 1, 1, 10, 0, 0) + upper_bound = datetime(2020, 1, 1, 18, 0, 0) + self.assertFalse(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) + + self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night + lower_bound = datetime(2020, 1, 1, 8, 0, 0) + upper_bound = datetime(2020, 1, 1, 10, 0, 0) + self.assertFalse(tc1.can_run_anywhere_within_timewindow_with_daily_constraints(self.scheduling_unit_blueprint, lower_bound, upper_bound)) + + def test_can_run_within_timewindow_with_twilight_constraint_returns_correct_value(self): + # todo: for time ranges across dates, consider removing the mock for this because the moving window cannot be easily mocked + # remove other constraints: + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky'] = {} + + # set constraint to test + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['avoid_twilight'] = True + self.scheduling_unit_blueprint.save() + + # can run in day + self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night + lower_bound = datetime(2020, 1, 1, 8, 0, 0) + upper_bound = datetime(2020, 1, 1, 15, 0, 0) + self.assertTrue(can_run_within_timewindow(self.scheduling_unit_blueprint, lower_bound, upper_bound)) + + # can run at night + self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night + lower_bound = datetime(2020, 1, 1, 15, 0, 0) + upper_bound = datetime(2020, 1, 1, 23, 0, 0) + self.assertTrue(can_run_within_timewindow(self.scheduling_unit_blueprint, lower_bound, upper_bound)) + + +class TestSkyConstraints(unittest.TestCase): + ''' + Tests for the constraint checkers used in dynamic scheduling + ''' + + def setUp(self) -> None: + # scheduling unit + self.obs_duration = 120 * 60 + scheduling_set = models.SchedulingSet.objects.create(**SchedulingSet_test_data()) + scheduling_unit_draft = TestDynamicScheduling.create_simple_observation_scheduling_unit("scheduling unit for ...%s" % self._testMethodName[30:], + scheduling_set=scheduling_set, + obs_duration=self.obs_duration) + self.scheduling_unit_blueprint = create_task_blueprints_and_subtasks_from_scheduling_unit_draft(scheduling_unit_draft) + + # mock out conversions for speedup and assertable timestamps + self.distance_data = { + "sun": {datetime(2020, 1, 1, 10, 0, 0): Angle("0.3rad"), datetime(2020, 1, 1, 12, 0, 0): Angle("0.35rad")}, + "moon": {datetime(2020, 1, 1, 10, 0, 0): Angle("0.2rad"), datetime(2020, 1, 1, 12, 0, 0): Angle("0.25rad")}, + "jupiter": {datetime(2020, 1, 1, 10, 0, 0): Angle("0.1rad"), datetime(2020, 1, 1, 12, 0, 0): Angle("0.15rad")} + } + self.distance_patcher = mock.patch('lofar.sas.tmss.services.scheduling.constraints.template_constraints_v1.coordinates_and_timestamps_to_separation_from_bodies') + self.distance_mock = self.distance_patcher.start() + self.distance_mock.return_value = self.distance_data + self.addCleanup(self.distance_patcher.stop) + + self.target_rise_and_set_data = {"CS002": [{"rise": datetime(2020, 1, 1, 8, 0, 0), "set": datetime(2020, 1, 1, 12, 30, 0)}, + {"rise": datetime(2020, 1, 1, 8, 0, 0), "set": datetime(2020, 1, 1, 12, 30, 0)}]} + self.target_rise_and_set_patcher = mock.patch('lofar.sas.tmss.services.scheduling.constraints.template_constraints_v1.coordinates_timestamps_and_stations_to_target_rise_and_set') + self.target_rise_and_set_mock = self.target_rise_and_set_patcher.start() + self.target_rise_and_set_mock.return_value = self.target_rise_and_set_data + self.addCleanup(self.target_rise_and_set_patcher.stop) + + # min_distance + + def test_can_run_anywhere_within_timewindow_with_sky_constraints_with_min_distance_constraint_returns_true_when_met(self): + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky'] = {'min_distance': {'sun': 0.1, 'moon': 0.1, 'jupiter': 0.1}} + self.scheduling_unit_blueprint.save() + timestamp = datetime(2020, 1, 1, 10, 0, 0) + returned_value = tc1.can_run_anywhere_within_timewindow_with_sky_constraints(self.scheduling_unit_blueprint, timestamp, timestamp + timedelta(seconds=self.obs_duration)) + self.assertTrue(returned_value) + + def test_can_run_anywhere_within_timewindow_with_sky_constraints_with_min_distance_constraint_returns_false_when_not_met(self): + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky'] = {'min_distance': {'sun': 0.2, 'moon': 0.2, 'jupiter': 0.2}} + self.scheduling_unit_blueprint.save() + timestamp = datetime(2020, 1, 1, 10, 0, 0) + returned_value = tc1.can_run_anywhere_within_timewindow_with_sky_constraints(self.scheduling_unit_blueprint, timestamp, timestamp + timedelta(seconds=self.obs_duration)) + self.assertFalse(returned_value) + + # min_target_elevation + + def test_can_run_anywhere_within_timewindow_with_sky_constraints_with_min_target_elevation_constraint_returns_true_when_met(self): + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky'] = {'min_target_elevation': 0.1} + self.scheduling_unit_blueprint.save() + timestamp = datetime(2020, 1, 1, 10, 0, 0) + returned_value = tc1.can_run_anywhere_within_timewindow_with_sky_constraints(self.scheduling_unit_blueprint, timestamp, timestamp + timedelta(seconds=self.obs_duration)) + self.assertTrue(returned_value) + + def test_can_run_anywhere_within_timewindow_with_sky_constraints_with_min_target_elevation_constraint_returns_false_when_not_met(self): + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky'] = {'min_target_elevation': 0.2} + self.scheduling_unit_blueprint.save() + timestamp = datetime(2020, 1, 1, 11, 0, 0) + returned_value = tc1.can_run_anywhere_within_timewindow_with_sky_constraints(self.scheduling_unit_blueprint, timestamp, timestamp + timedelta(seconds=self.obs_duration)) + self.assertFalse(returned_value) class TestTimeConstraints(TestCase):