diff --git a/SAS/TMSS/services/scheduling/test/t_dynamic_scheduling.py b/SAS/TMSS/services/scheduling/test/t_dynamic_scheduling.py index eec91e72c8b90c8b4284c8058ec2b4fdcefc289c..343cc8925a42702dc814b06c808c68d9498f95ee 100755 --- a/SAS/TMSS/services/scheduling/test/t_dynamic_scheduling.py +++ b/SAS/TMSS/services/scheduling/test/t_dynamic_scheduling.py @@ -134,579 +134,579 @@ class TestDynamicScheduling(TestCase): # Note: we use django.test.TestCase inst observation_strategy_template=strategy_template, scheduling_constraints_doc=constraints, scheduling_constraints_template=constraints_template) -# -# -# 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 schedunits, 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_high.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') -# -# -# 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 -# 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']['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']['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']['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']['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)) + + + 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 schedunits, 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_high.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') + + + 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 + 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']['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']['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']['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']['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):