def test_filter_on_pending(self):
        request_id = 5
        r1 = Request(
            configurations=None,
            windows=Windows(),
            request_id=request_id,
            state='PENDING'
        )
        r2 = Request(
            configurations=None,
            windows=Windows(),
            request_id=9,
            state='UNSCHEDULABLE'
        )
        rg1 = RequestGroup(
            operator='single',
            requests=[r1, r2],
            proposal=None,
            expires=None,
            rg_id=1,
            is_staff=False,
            name=None,
            ipp_value=1.0,
            observation_type='NORMAL',
            submitter='',
        )

        filter_on_pending([rg1])

        assert_equal(rg1.requests, [r1])
    def test_has_windows_windows(self):
        window_dict = {
            'start': "2013-03-01T00:00:00Z",
            'end': "2013-03-01T00:30:00Z",
        }
        w = Window(window_dict=window_dict, resource=self.t1['name'])
        windows = Windows()
        windows.append(w)

        assert_equal(windows.has_windows(), True)
Esempio n. 3
0
def create_request_group(window_dicts,
                         operator='and',
                         resource_name='Martin',
                         configurations=None,
                         proposal=create_mock_proposal(),
                         expires=None,
                         duration=60,
                         first_request_id=5,
                         request_group_id=5):
    t1 = {'name': resource_name}

    req_list = []
    window_list = []
    next_request_id = int(first_request_id)
    for req_windows in window_dicts:
        windows = Windows()
        for window_dict in req_windows:
            w = Window(window_dict=window_dict, resource=t1['name'])
            windows.append(w)
            window_list.append(w)

        r = Request(configurations=configurations,
                    windows=windows,
                    request_id=next_request_id)

        r.get_duration = Mock(return_value=duration)

        req_list.append(r)
        next_request_id += 1

    if len(req_list) == 1:
        operator = 'single'

    if expires:
        RequestGroup.expires = PropertyMock(return_value=expires)
    else:
        RequestGroup.expires = PropertyMock(return_value=datetime.utcnow() +
                                            timedelta(days=365))
    rg = RequestGroup(
        operator=operator,
        requests=req_list,
        proposal=proposal,
        expires=None,
        rg_id=request_group_id,
        is_staff=False,
        name=None,
        ipp_value=1.0,
        observation_type='NORMAL',
        submitter='',
    )

    return rg, window_list
Esempio n. 4
0
def intervals_to_windows(req, intersections_for_resource):
    windows = Windows()
    for resource_name, intervals in intersections_for_resource.items():
        windows_for_resource = req.windows.windows_for_resource[resource_name]
        # TODO: This needs cleanup
        # It's possible there are no windows for this resource so we can't
        # assume that we will be able to get a handle on the resource from the
        # first window.
        if (len(windows_for_resource) > 0):
            resource = windows_for_resource[0].resource

            for (start, end) in intervals.toTupleList():
                w = Window({'start': start, 'end': end}, resource)
                windows.append(w)

    return windows
    def test_large_and_requests(self):
        days_out = 0
        # build up a request a day for 100 days out
        new_time = datetime(2016, 10, 3, 5, 0)
        request_list = []
        while days_out < 80:
            resource = '1m0a.doma.ogg'
            window = Window(
                {
                    'start': new_time + timedelta(days=days_out),
                    'end':
                    new_time + timedelta(days=days_out, hours=0, minutes=30)
                }, resource)
            windows = Windows()
            windows.append(window)
            request = Request(configurations=[self.configuration],
                              windows=windows,
                              request_id=int("11{}".format(days_out).rjust(
                                  10, '0')),
                              duration=1750)
            request_list.append(request)
            days_out += 1

        request_group = RequestGroup(operator='and',
                                     requests=request_list,
                                     proposal=self.proposal,
                                     expires=datetime(2050, 1, 1),
                                     rg_id=100,
                                     is_staff=False,
                                     ipp_value=1.0,
                                     name='large ur',
                                     submitter='',
                                     observation_type='NORMAL')

        normal_request_list = [
            request_group,
        ]
        result = self._schedule_requests([], normal_request_list,
                                         new_time - timedelta(hours=10))
        scheduled_rgs = result.get_scheduled_requests_by_request_group_id()

        # assert that none of the and is scheduled (since it has an unschedulable request in it)
        # assert that both of the manys are scheduled
        assert 100 in scheduled_rgs
        for req in request_list:
            # assert each child request is in the schedule (scheduler schedules past horizon for ands)
            assert req.id in scheduled_rgs[100]
    def test_multiple_user_intervals_are_honoured(self):
        # A one day user supplied window
        windows = [{
            'start': datetime(2011, 11, 1, 6, 0, 0),
            'end': datetime(2011, 11, 1, 9, 0, 0)
        }, {
            'start': datetime(2011, 11, 2, 1, 0, 0),
            'end': datetime(2011, 11, 2, 4, 0, 0)
        }]

        dt_windows = Windows()
        resource_name = '1m0a.doma.bpl'
        for w in windows:
            dt_windows.append(Window(w, self.tels[resource_name]['name']))

        req = Request(configurations=[self.configuration],
                      windows=dt_windows,
                      request_id='1')

        visibilities = construct_visibilities(self.tels, self.start, self.end)

        intervals_for_resource = self.make_rise_set_intervals(
            req, visibilities)
        compute_request_availability(req, intervals_for_resource, {})
        received = req_windows_to_kernel_intervals(
            req.windows.windows_for_resource)

        # The user windows constrain the available observing windows (compare to
        # previous tests)
        date_format = '%Y-%m-%d %H:%M:%S.%f'
        rise_set_dark_intervals = (
            datetime.strptime('2011-11-01 06:00:00.0', date_format),
            datetime.strptime('2011-11-01 07:52:00.564199', date_format),
            datetime.strptime('2011-11-02 02:01:50.423880', date_format),
            datetime.strptime('2011-11-02 04:00:00.0', date_format),
        )

        # Verify we get the intervals we expect
        for resource_name, received_intervals in received.items():
            for i, received_tp in enumerate(received_intervals.toDictList()):
                assert_equal(received_tp['time'], rise_set_dark_intervals[i])
    def make_constrained_request(self,
                                 airmass=None,
                                 start=datetime(2011, 11, 1, 6, 0, 0),
                                 end=datetime(2011, 11, 2, 6, 0, 0)):
        # A one day user supplied window
        window_dict = {'start': start, 'end': end}
        resource_name = '1m0a.doma.bpl'
        resource = self.tels[resource_name]

        window = Window(window_dict, resource['name'])
        dt_windows = Windows()
        dt_windows.append(window)

        configuration = copy.deepcopy(self.configuration)
        configuration.constraints['max_airmass'] = airmass

        req = Request(configurations=[configuration],
                      windows=dt_windows,
                      request_id=1,
                      duration=10)

        return req
    def test_is_empty_has_windows_empty_on_one_resource(self):
        window_dict = {
            'start': "2013-03-01T00:00:00Z",
            'end': "2013-03-01T00:30:00Z",
        }
        w = Window(window_dict=window_dict, resource=self.t1['name'])
        w2 = Window(window_dict=window_dict, resource=self.t2['name'])

        windows = Windows()
        windows.append(w)
        windows.append(w2)
        windows.windows_for_resource[self.t2['name']] = []

        assert_equal(windows.has_windows(), True)
        assert_equal(windows.size(), 1)
    def test_make_target_intervals(self):
        window_dict = {'start': self.start, 'end': self.end}
        resource_name = '1m0a.doma.bpl'
        resource = self.tels[resource_name]

        window = Window(window_dict, resource['name'])
        dt_windows = Windows()
        dt_windows.append(window)

        req = Request(configurations=[self.configuration],
                      windows=dt_windows,
                      request_id='1')

        visibilities = construct_visibilities(self.tels, self.start, self.end)

        intervals_for_resource = self.make_rise_set_intervals(
            req, visibilities)
        compute_request_availability(req, intervals_for_resource, {})
        received = req_windows_to_kernel_intervals(
            req.windows.windows_for_resource)

        date_format = '%Y-%m-%d %H:%M:%S.%f'
        rise_set_dark_intervals = (datetime.strptime(
            '2011-11-01 02:02:43.257196', date_format),
                                   datetime.strptime(
                                       '2011-11-01 07:52:00.564199',
                                       date_format),
                                   datetime.strptime(
                                       '2011-11-02 02:01:50.423880',
                                       date_format),
                                   datetime.strptime(
                                       '2011-11-02 07:48:04.692316',
                                       date_format))

        # Verify we get the intervals we expect
        for resource_name, received_intervals in received.items():
            for i, received_tp in enumerate(received_intervals.toDictList()):
                assert_equal(received_tp['time'], rise_set_dark_intervals[i])
 def test_drop_empty_requests(self):
     request_id = 5
     r = Request(
         configurations=None,
         windows=Windows(),
         request_id=request_id
     )
     rg1 = RequestGroup(
         operator='single',
         requests=[r],
         proposal=None,
         expires=None,
         rg_id=1,
         is_staff=False,
         name=None,
         ipp_value=1.0,
         observation_type='NORMAL',
         submitter='',
     )
     received = drop_empty_requests([rg1])
     assert_equal(received, [5])
    def test_visibility_intervals_at_low_horizon_are_allowed_by_hour_angle(
            self):

        window_dict = {
            'start': datetime(2013, 3, 22, 0, 0, 0),
            'end': datetime(2013, 3, 23, 0, 0, 0),
        }

        tel_name = '1m0a.doma.coj'
        tel = dict(name=tel_name,
                   tel_class='1m0',
                   latitude=-31.273,
                   longitude=149.070593,
                   horizon=15,
                   ha_limit_neg=-4.6,
                   ha_limit_pos=4.6,
                   zenith_blind_spot=0.0)

        tels = {
            tel_name: tel,
        }

        target = ICRSTarget(
            # RA 15:41:25.91
            ra=235.357958333,
            dec=-60.0,
        )

        window = Window(window_dict, tel['name'])
        dt_windows = Windows()
        dt_windows.append(window)

        configuration = copy.deepcopy(self.configuration)
        configuration.target = target

        req = Request(
            configurations=[configuration],
            windows=dt_windows,
            request_id='1',
            duration=10,
        )
        sem_start = datetime(2013, 3, 1, 0, 0, 0)
        sem_end = datetime(2013, 3, 31, 0, 0, 0)

        visibilities = construct_visibilities(tels, sem_start, sem_end)

        intervals_for_resource = self.make_rise_set_intervals(
            req, visibilities)
        compute_request_availability(req, intervals_for_resource, {})
        received = req_windows_to_kernel_intervals(
            req.windows.windows_for_resource)

        # Hour angle not violated independently confirmed by hand-cranking through SLALIB
        expected_tps = [
            {
                'type': 'start',
                'time': datetime(2013, 3, 22, 13, 9, 28, 988253)
            },
            {
                'type': 'end',
                'time': datetime(2013, 3, 22, 19, 16, 27, 292072)
            },
        ]

        for received_tp, expected_tp in zip(received[tel_name].toDictList(),
                                            expected_tps):
            assert_equal(received_tp['type'], expected_tp['type'])
            assert_equal(received_tp['time'], expected_tp['time'])
 def test_has_windows_no_windows(self):
     windows = Windows()
     assert_equal(windows.has_windows(), False)
    def _build_request_group(self, base_priority=1.0, ipp_value=1.0):
        operator = 'single'

        proposal = Proposal(id='LCOSchedulerTest',
                            pi='Eric Saunders',
                            tag='admin',
                            tac_priority=base_priority)

        instrument_config = dict(exposure_count=1,
                                 bin_x=2,
                                 bin_y=2,
                                 exposure_time=20,
                                 extra_params={},
                                 optical_elements={'filter': 'BSSL-UX-020'})

        guiding_config = dict(mode='ON',
                              optional=True,
                              optical_elements={},
                              extra_params={},
                              exposure_time=10)

        acquisition_config = dict(mode='OFF', extra_params={})

        constraints = {'max_airmass': None, 'min_lunar_distance': 0.0}

        configuration1 = Configuration(
            dict(id=5,
                 target=None,
                 type='expose',
                 instrument_type='1M0-SCICAM-SBIG',
                 priority=1,
                 instrument_configs=[instrument_config],
                 acquisition_config=acquisition_config,
                 guiding_config=guiding_config,
                 extra_params={},
                 constraints=constraints))

        telescope = dict(
            name='maui',
            latitude=20.7069444444,
            longitude=-156.258055556,
        )
        window_dict = {
            'start': "2013-03-01T00:00:00Z",
            'end': "2013-03-01T00:30:00Z",
        }
        w = Window(window_dict=window_dict, resource=telescope['name'])
        windows = Windows()
        windows.append(w)

        r = Request(configurations=[configuration1],
                    windows=windows,
                    request_id='0000000003',
                    duration=10)

        rg = RequestGroup(operator=operator,
                          requests=[r],
                          proposal=proposal,
                          expires=None,
                          rg_id=4,
                          is_staff=False,
                          ipp_value=ipp_value,
                          observation_type='NORMAL',
                          name=None,
                          submitter='Eric Saunders')

        return rg
    def setup(self):
        self.target = ICRSTarget(
            name='deneb',
            ra=310.35795833333333,
            dec=45.280338888888885,
            epoch=2000,
        )

        self.telescope = dict(name='1m0a.doma.ogg',
                              latitude=20.7069444444,
                              longitude=-156.258055556,
                              tel_class='1m0',
                              horizon=15,
                              status='online',
                              ha_limit_neg=-4.6,
                              ha_limit_pos=4.6,
                              zenith_blind_spot=0.0)
        self.telescopes = {'1m0a.doma.ogg': self.telescope}

        self.proposal = Proposal(id='LCOSchedulerTest',
                                 pi='Eric Saunders',
                                 tag='admin',
                                 tac_priority=1)

        self.instrument_config = dict(exposure_count=1,
                                      bin_x=2,
                                      bin_y=2,
                                      exposure_time=60 * 25,
                                      optical_elements={'filter': 'b'})

        self.guiding_config = dict(mode='ON',
                                   optional=True,
                                   optical_elements={},
                                   exposure_time=10)

        self.acquisition_config = dict(mode='OFF')

        self.constraints = {'max_airmass': None, 'min_lunar_distance': 0}

        self.configuration = Configuration(
            **dict(id=5,
                   target=self.target,
                   type='expose',
                   instrument_type='1M0-SCICAM-SBIG',
                   priority=1,
                   instrument_configs=[self.instrument_config],
                   acquisition_config=self.acquisition_config,
                   guiding_config=self.guiding_config,
                   constraints=self.constraints))

        self.base_time = datetime(2016, 9, 14, 6, 0)

        resource_1 = '1m0a.doma.ogg'
        self.window_1 = Window(
            {
                'start': self.base_time,
                'end': self.base_time + timedelta(hours=0, minutes=30)
            }, resource_1)
        self.windows_1 = Windows()
        self.windows_1.append(self.window_1)

        resource_2 = '1m0a.doma.ogg'
        self.window_2 = Window(
            {
                'start': self.base_time + timedelta(hours=0, minutes=30),
                'end': self.base_time + timedelta(hours=1, minutes=0)
            }, resource_2)
        self.windows_2 = Windows()
        self.windows_2.append(self.window_2)
        self.resource_3 = '1m0a.doma.ogg'
        self.window_3 = Window(
            {
                'start': self.base_time + timedelta(hours=1, minutes=0),
                'end': self.base_time + timedelta(hours=1, minutes=30)
            }, self.resource_3)
        self.windows_3 = Windows()
        self.windows_3.append(self.window_3)

        self.request_1 = Request(configurations=[self.configuration],
                                 windows=self.windows_1,
                                 request_id=1,
                                 duration=1750)

        self.request_2 = Request(configurations=[self.configuration],
                                 windows=self.windows_2,
                                 request_id=2,
                                 duration=1750)

        self.request_3 = Request(configurations=[self.configuration],
                                 windows=self.windows_2,
                                 request_id=3,
                                 duration=1750)

        self.request_4 = Request(configurations=[self.configuration],
                                 windows=self.windows_3,
                                 request_id=4,
                                 duration=1750)

        self.request_5 = Request(configurations=[self.configuration],
                                 windows=self.windows_3,
                                 request_id=5,
                                 duration=1750)

        self.and_request_group_1 = RequestGroup(
            operator='and',
            requests=[self.request_1, self.request_2],
            proposal=self.proposal,
            expires=datetime(2050, 1, 1),
            rg_id=1,
            is_staff=False,
            observation_type='NORMAL',
            ipp_value=1.0,
            name='ur 1',
            submitter='')
        self.and_request_group_2 = RequestGroup(
            operator='and',
            requests=[self.request_3, self.request_4],
            proposal=self.proposal,
            expires=datetime(2050, 1, 1),
            rg_id=2,
            is_staff=False,
            observation_type='NORMAL',
            ipp_value=1.0,
            name='ur 2',
            submitter='')
        self.many_request_group_1 = RequestGroup(
            operator='many',
            requests=[self.request_1, self.request_2],
            proposal=self.proposal,
            expires=datetime(2050, 1, 1),
            rg_id=3,
            is_staff=False,
            observation_type='NORMAL',
            ipp_value=1.5,
            name='ur 3',
            submitter='')
        self.many_request_group_2 = RequestGroup(
            operator='many',
            requests=[self.request_3, self.request_4],
            proposal=self.proposal,
            expires=datetime(2050, 1, 1),
            rg_id=4,
            is_staff=False,
            observation_type='NORMAL',
            ipp_value=1.5,
            name='ur 4',
            submitter='')
        self.rr_request_group_1 = RequestGroup(
            operator='many',
            requests=[self.request_5],
            proposal=self.proposal,
            expires=datetime(2050, 1, 1),
            rg_id=5,
            is_staff=False,
            observation_type='RAPID_RESPONSE',
            ipp_value=1.5,
            name='ur 5',
            submitter='')
        self.rr_request_group_2 = RequestGroup(
            operator='many',
            requests=[self.request_1, self.request_3],
            proposal=self.proposal,
            expires=datetime(2050, 1, 1),
            rg_id=6,
            is_staff=False,
            observation_type='RAPID_RESPONSE',
            ipp_value=1.5,
            name='ur 6',
            submitter='')
class TestIntegration(object):
    '''Unit tests for the adaptive scheduler Request object.'''
    def setup(self):
        self.target = ICRSTarget(
            name='deneb',
            ra=310.35795833333333,
            dec=45.280338888888885,
            epoch=2000,
        )

        self.telescope = dict(name='1m0a.doma.ogg',
                              latitude=20.7069444444,
                              longitude=-156.258055556,
                              tel_class='1m0',
                              horizon=15,
                              status='online',
                              ha_limit_neg=-4.6,
                              ha_limit_pos=4.6,
                              zenith_blind_spot=0.0)
        self.telescopes = {'1m0a.doma.ogg': self.telescope}

        self.proposal = Proposal(id='LCOSchedulerTest',
                                 pi='Eric Saunders',
                                 tag='admin',
                                 tac_priority=1)

        self.instrument_config = dict(exposure_count=1,
                                      bin_x=2,
                                      bin_y=2,
                                      exposure_time=60 * 25,
                                      optical_elements={'filter': 'b'})

        self.guiding_config = dict(mode='ON',
                                   optional=True,
                                   optical_elements={},
                                   exposure_time=10)

        self.acquisition_config = dict(mode='OFF')

        self.constraints = {'max_airmass': None, 'min_lunar_distance': 0}

        self.configuration = Configuration(
            **dict(id=5,
                   target=self.target,
                   type='expose',
                   instrument_type='1M0-SCICAM-SBIG',
                   priority=1,
                   instrument_configs=[self.instrument_config],
                   acquisition_config=self.acquisition_config,
                   guiding_config=self.guiding_config,
                   constraints=self.constraints))

        self.base_time = datetime(2016, 9, 14, 6, 0)

        resource_1 = '1m0a.doma.ogg'
        self.window_1 = Window(
            {
                'start': self.base_time,
                'end': self.base_time + timedelta(hours=0, minutes=30)
            }, resource_1)
        self.windows_1 = Windows()
        self.windows_1.append(self.window_1)

        resource_2 = '1m0a.doma.ogg'
        self.window_2 = Window(
            {
                'start': self.base_time + timedelta(hours=0, minutes=30),
                'end': self.base_time + timedelta(hours=1, minutes=0)
            }, resource_2)
        self.windows_2 = Windows()
        self.windows_2.append(self.window_2)
        self.resource_3 = '1m0a.doma.ogg'
        self.window_3 = Window(
            {
                'start': self.base_time + timedelta(hours=1, minutes=0),
                'end': self.base_time + timedelta(hours=1, minutes=30)
            }, self.resource_3)
        self.windows_3 = Windows()
        self.windows_3.append(self.window_3)

        self.request_1 = Request(configurations=[self.configuration],
                                 windows=self.windows_1,
                                 request_id=1,
                                 duration=1750)

        self.request_2 = Request(configurations=[self.configuration],
                                 windows=self.windows_2,
                                 request_id=2,
                                 duration=1750)

        self.request_3 = Request(configurations=[self.configuration],
                                 windows=self.windows_2,
                                 request_id=3,
                                 duration=1750)

        self.request_4 = Request(configurations=[self.configuration],
                                 windows=self.windows_3,
                                 request_id=4,
                                 duration=1750)

        self.request_5 = Request(configurations=[self.configuration],
                                 windows=self.windows_3,
                                 request_id=5,
                                 duration=1750)

        self.and_request_group_1 = RequestGroup(
            operator='and',
            requests=[self.request_1, self.request_2],
            proposal=self.proposal,
            expires=datetime(2050, 1, 1),
            rg_id=1,
            is_staff=False,
            observation_type='NORMAL',
            ipp_value=1.0,
            name='ur 1',
            submitter='')
        self.and_request_group_2 = RequestGroup(
            operator='and',
            requests=[self.request_3, self.request_4],
            proposal=self.proposal,
            expires=datetime(2050, 1, 1),
            rg_id=2,
            is_staff=False,
            observation_type='NORMAL',
            ipp_value=1.0,
            name='ur 2',
            submitter='')
        self.many_request_group_1 = RequestGroup(
            operator='many',
            requests=[self.request_1, self.request_2],
            proposal=self.proposal,
            expires=datetime(2050, 1, 1),
            rg_id=3,
            is_staff=False,
            observation_type='NORMAL',
            ipp_value=1.5,
            name='ur 3',
            submitter='')
        self.many_request_group_2 = RequestGroup(
            operator='many',
            requests=[self.request_3, self.request_4],
            proposal=self.proposal,
            expires=datetime(2050, 1, 1),
            rg_id=4,
            is_staff=False,
            observation_type='NORMAL',
            ipp_value=1.5,
            name='ur 4',
            submitter='')
        self.rr_request_group_1 = RequestGroup(
            operator='many',
            requests=[self.request_5],
            proposal=self.proposal,
            expires=datetime(2050, 1, 1),
            rg_id=5,
            is_staff=False,
            observation_type='RAPID_RESPONSE',
            ipp_value=1.5,
            name='ur 5',
            submitter='')
        self.rr_request_group_2 = RequestGroup(
            operator='many',
            requests=[self.request_1, self.request_3],
            proposal=self.proposal,
            expires=datetime(2050, 1, 1),
            rg_id=6,
            is_staff=False,
            observation_type='RAPID_RESPONSE',
            ipp_value=1.5,
            name='ur 6',
            submitter='')

    def _schedule_requests(self,
                           rr_rg_list,
                           normal_rg_list,
                           scheduler_time,
                           rr_loop=False,
                           block_schedule_by_resource=None,
                           running_request_groups=None,
                           rapid_response_ids=None,
                           semester_details=None):
        if block_schedule_by_resource is None:
            block_schedule_by_resource = {}
        if running_request_groups is None:
            running_request_groups = []
        if rapid_response_ids is None:
            rapid_response_ids = []
        if semester_details is None:
            semester_details = {}
        sched_params = SchedulerParameters(run_once=True,
                                           dry_run=True,
                                           timelimit_seconds=30)
        event_bus_mock = Mock()
        scheduler = LCOGTNetworkScheduler(FullScheduler_ortoolkit,
                                          sched_params, event_bus_mock,
                                          self.telescopes)
        network_interface_mock = Mock()
        network_interface_mock.cancel = Mock(return_value=0)
        network_interface_mock.save = Mock(return_value=0)
        network_interface_mock.abort = Mock(return_value=0)
        network_interface_mock.get_current_events = Mock(return_value={})

        mock_input_factory = create_scheduler_input_factory(
            rr_rg_list, normal_rg_list, block_schedule_by_resource,
            running_request_groups, rapid_response_ids)

        if rr_loop:
            scheduler_input = mock_input_factory.create_rr_scheduling_input()
        else:
            scheduler_input = mock_input_factory.create_normal_scheduling_input(
            )
        scheduler_input.scheduler_now = scheduler_time
        scheduler_input.estimated_scheduler_end = scheduler_time + timedelta(
            minutes=15)
        if not semester_details:
            semester_details = {
                'id': '2015A',
                'start': scheduler_time - timedelta(days=150),
                'end': scheduler_time + timedelta(days=150)
            }

        result = scheduler.run_scheduler(scheduler_input,
                                         scheduler_time +
                                         timedelta(minutes=15),
                                         semester_details,
                                         preemption_enabled=rr_loop)

        return result

    def test_changing_semester_details_clears_visibility_cache(self):
        scheduler_time = self.base_time - timedelta(hours=10)
        sched_params = SchedulerParameters(run_once=True,
                                           dry_run=True,
                                           timelimit_seconds=30)
        event_bus_mock = Mock()
        scheduler = LCOGTNetworkScheduler(FullScheduler_ortoolkit,
                                          sched_params, event_bus_mock,
                                          self.telescopes)
        network_interface_mock = Mock()
        network_interface_mock.cancel = Mock(return_value=0)
        network_interface_mock.save = Mock(return_value=0)
        network_interface_mock.abort = Mock(return_value=0)
        network_interface_mock.get_current_events = Mock(return_value={})
        normal_ur_list = [self.and_request_group_1, self.and_request_group_2]
        mock_input_factory = create_scheduler_input_factory([], normal_ur_list,
                                                            {}, [], [])
        scheduler_input = mock_input_factory.create_normal_scheduling_input()
        scheduler_input.scheduler_now = scheduler_time
        scheduler_input.estimated_scheduler_end = scheduler_time + timedelta(
            minutes=15)
        semester_details = {
            'id': '2015A',
            'start': scheduler_time - timedelta(days=150),
            'end': scheduler_time + timedelta(days=150)
        }

        scheduler.run_scheduler(scheduler_input,
                                scheduler_time + timedelta(minutes=15),
                                semester_details,
                                preemption_enabled=False)
        assert scheduler.visibility_cache != {}
        saved_visibility_cache = scheduler.visibility_cache
        # Now run again with a different semester to clear visibility cache
        semester_details['start'] = scheduler_time - timedelta(days=149)
        scheduler.run_scheduler(scheduler_input,
                                scheduler_time + timedelta(minutes=15),
                                semester_details,
                                preemption_enabled=False)
        assert scheduler.visibility_cache != {}
        assert scheduler.visibility_cache != saved_visibility_cache

    def test_competing_and_requests(self):
        result = self._schedule_requests(
            [], [self.and_request_group_1, self.and_request_group_2],
            self.base_time - timedelta(hours=10))

        scheduled_rgs = result.get_scheduled_requests_by_request_group_id()

        # assert that either user request 1 or user request 2 were scheduled in full, with the other not being scheduled
        if 1 in scheduled_rgs:
            # check that ur 1s requests are scheduled
            assert 1 in scheduled_rgs[1]
            assert 2 in scheduled_rgs[1]
            # and check that ur 2 is not scheduled
            assert 2 not in scheduled_rgs
        else:
            assert 4 in scheduled_rgs[2]
            assert 3 in scheduled_rgs[2]
            assert 1 not in scheduled_rgs

    def test_competing_many_requests(self):
        result = self._schedule_requests(
            [], [self.many_request_group_1, self.many_request_group_2],
            self.base_time - timedelta(hours=10))
        scheduled_rgs = result.get_scheduled_requests_by_request_group_id()

        # assert that user request 3 request 1 and user request 4 request 4 were scheduled ,
        # along with one of either 3-2 or 4-3.
        assert 3 in scheduled_rgs
        assert 4 in scheduled_rgs
        assert 1 in scheduled_rgs[3]
        assert 4 in scheduled_rgs[4]
        if 2 in scheduled_rgs[3]:
            assert 3 not in scheduled_rgs[4]
        else:
            assert 2 not in scheduled_rgs[3]

    def test_competing_many_and_requests(self):
        normal_request_list = [
            self.and_request_group_1, self.many_request_group_2
        ]
        result = self._schedule_requests([], normal_request_list,
                                         self.base_time - timedelta(hours=10))
        scheduled_rgs = result.get_scheduled_requests_by_request_group_id()

        # assert the and request was taken in full, and the remaining many ur 4 request 2 was scheduled
        assert 1 in scheduled_rgs
        assert 1 in scheduled_rgs[1]
        assert 2 in scheduled_rgs[1]
        assert 4 in scheduled_rgs
        # the second request from the many was scheduled but the first was not
        assert 4 in scheduled_rgs[4]
        assert 3 not in scheduled_rgs[4]

    def test_large_and_requests(self):
        days_out = 0
        # build up a request a day for 100 days out
        new_time = datetime(2016, 10, 3, 5, 0)
        request_list = []
        while days_out < 80:
            resource = '1m0a.doma.ogg'
            window = Window(
                {
                    'start': new_time + timedelta(days=days_out),
                    'end':
                    new_time + timedelta(days=days_out, hours=0, minutes=30)
                }, resource)
            windows = Windows()
            windows.append(window)
            request = Request(configurations=[self.configuration],
                              windows=windows,
                              request_id=int("11{}".format(days_out).rjust(
                                  10, '0')),
                              duration=1750)
            request_list.append(request)
            days_out += 1

        request_group = RequestGroup(operator='and',
                                     requests=request_list,
                                     proposal=self.proposal,
                                     expires=datetime(2050, 1, 1),
                                     rg_id=100,
                                     is_staff=False,
                                     ipp_value=1.0,
                                     name='large ur',
                                     submitter='',
                                     observation_type='NORMAL')

        normal_request_list = [
            request_group,
        ]
        result = self._schedule_requests([], normal_request_list,
                                         new_time - timedelta(hours=10))
        scheduled_rgs = result.get_scheduled_requests_by_request_group_id()

        # assert that none of the and is scheduled (since it has an unschedulable request in it)
        # assert that both of the manys are scheduled
        assert 100 in scheduled_rgs
        for req in request_list:
            # assert each child request is in the schedule (scheduler schedules past horizon for ands)
            assert req.id in scheduled_rgs[100]

    def test_normal_requests_dont_schedule_over_rr(self):
        ''' Verifies that a normal request will not schedule over a just scheduled RR request
        '''
        rr_schedule = {
            self.resource_3: {
                'all': [
                    (self.base_time + timedelta(hours=1, minutes=0),
                     self.base_time + timedelta(hours=1, minutes=25)),
                ]
            }
        }
        result = self._schedule_requests(
            [
                self.rr_request_group_1,
            ], [
                self.many_request_group_2,
            ],
            self.base_time - timedelta(hours=10),
            rr_loop=False,
            block_schedule_by_resource=rr_schedule)
        scheduled_rgs = result.get_scheduled_requests_by_request_group_id()
        # Ensure request 3 could be scheduled, but request 4 could not because it overlapped with the scheduled RR
        assert_true(4 in scheduled_rgs)
        assert_true(4 not in scheduled_rgs[4])
        assert_true(3 in scheduled_rgs[4])

    def test_rr_requests_dont_schedule_over_running_rr(self):
        ''' Verifies that a RR will not preempt a currently running RR if it overlaps with its window completely
        '''
        rapid_response_id = 99
        running_request_group = create_running_request_group(
            request_group_id=rapid_response_id,
            request_id=99,
            resource=self.resource_3,
            start=self.base_time,
            end=self.base_time + timedelta(hours=2))
        result = self._schedule_requests([
            self.rr_request_group_1,
        ], [
            self.many_request_group_2,
        ],
                                         self.base_time - timedelta(hours=10),
                                         rr_loop=True,
                                         block_schedule_by_resource={},
                                         running_request_groups=[
                                             running_request_group,
                                         ],
                                         rapid_response_ids=[
                                             rapid_response_id,
                                         ])
        scheduled_rgs = result.get_scheduled_requests_by_request_group_id()
        # Ensure no RR was scheduled because the running request group was over it's time
        assert_false(5 in scheduled_rgs)
        assert_equal(scheduled_rgs, {})

    def test_rr_requests_do_schedule_over_running_normal(self):
        ''' Verifies that a RR will preempt a currently running normal request and be scheduled over it at its
            earliest time possible
        '''
        rapid_response_id = 777
        running_request_group = create_running_request_group(
            request_group_id=99,
            request_id=99,
            resource=self.resource_3,
            start=self.base_time,
            end=self.base_time + timedelta(hours=2))
        scheduler_start = self.base_time - timedelta(hours=10)
        result = self._schedule_requests([
            self.rr_request_group_1,
        ], [
            self.many_request_group_2,
        ],
                                         scheduler_start,
                                         rr_loop=True,
                                         block_schedule_by_resource={},
                                         running_request_groups=[
                                             running_request_group,
                                         ],
                                         rapid_response_ids=[
                                             rapid_response_id,
                                         ])
        scheduled_rgs = result.get_scheduled_requests_by_request_group_id()
        # Ensure RR was scheduled at its first time even though it overlaps with the currently running normal request
        assert_true(5 in scheduled_rgs)
        assert_true(5 in scheduled_rgs[5])
        semester_start = scheduler_start - timedelta(days=150)
        dt_start, dt_end = get_reservation_datetimes(scheduled_rgs[5][5],
                                                     semester_start)
        assert_equal(dt_start, self.window_3.start)
        assert_equal(dt_end, self.window_3.start + timedelta(seconds=1750))

    def test_rr_requests_schedule_after_running_rr(self):
        ''' Verifies that a RR will be scheduled after a currently running RR if it is able
        '''
        rapid_response_id = 99
        running_request_group = create_running_request_group(
            request_group_id=rapid_response_id,
            request_id=99,
            resource=self.resource_3,
            start=self.base_time,
            end=self.base_time + timedelta(hours=1, minutes=0, seconds=30))
        scheduler_start = self.base_time - timedelta(hours=10)
        result = self._schedule_requests([
            self.rr_request_group_1,
        ], [
            self.many_request_group_2,
        ],
                                         scheduler_start,
                                         rr_loop=True,
                                         block_schedule_by_resource={},
                                         running_request_groups=[
                                             running_request_group,
                                         ],
                                         rapid_response_ids=[
                                             rapid_response_id,
                                         ])
        scheduled_rgs = result.get_scheduled_requests_by_request_group_id()
        # Ensure RR was scheduled after the running RR since there was still time
        assert_true(5 in scheduled_rgs)
        assert_true(5 in scheduled_rgs[5])
        semester_start = scheduler_start - timedelta(days=150)
        dt_start, dt_end = get_reservation_datetimes(scheduled_rgs[5][5],
                                                     semester_start)
        assert_equal(
            dt_start,
            self.base_time + timedelta(hours=1, minutes=0, seconds=30))
        assert_equal(
            dt_end,
            self.base_time + timedelta(hours=1, minutes=0, seconds=30) +
            timedelta(seconds=1750))

    def test_normal_requests_dont_schedule_over_running_rr(self):
        ''' Verifies that a normal request will be blocked by a currently running RR
        '''
        rr_request_group_id = 99
        running_request_group = create_running_request_group(
            request_group_id=rr_request_group_id,
            request_id=99,
            resource=self.resource_3,
            start=self.base_time,
            end=self.base_time + timedelta(hours=2))
        result = self._schedule_requests([
            self.rr_request_group_1,
        ], [
            self.many_request_group_2,
        ],
                                         self.base_time - timedelta(hours=10),
                                         rr_loop=False,
                                         block_schedule_by_resource={},
                                         running_request_groups=[
                                             running_request_group,
                                         ],
                                         rapid_response_ids=[
                                             rr_request_group_id,
                                         ])
        scheduled_rgs = result.get_scheduled_requests_by_request_group_id()
        # Ensure request 3 could be scheduled, but request 4 could not because it overlapped with the scheduled RR
        assert_false(4 in scheduled_rgs)
        assert_false(3 in scheduled_rgs)
        assert_equal(scheduled_rgs, {})

    def test_normal_requests_can_schedule_after_rr(self):
        ''' Verifies that a normal request will respect a previously scheduled RR whose time overlaps with it's window.
            Ensures that the normal request starts after the end of the RR.
        '''
        rr_schedule = {
            self.resource_3: {
                'all': [
                    (self.base_time + timedelta(hours=1, minutes=0),
                     self.base_time +
                     timedelta(hours=1, minutes=0, seconds=30)),
                ]
            }
        }
        scheduler_start = self.base_time - timedelta(hours=10)
        result = self._schedule_requests(
            [
                self.rr_request_group_1,
            ], [
                self.many_request_group_2,
            ],
            scheduler_start,
            rr_loop=False,
            block_schedule_by_resource=rr_schedule)
        scheduled_rgs = result.get_scheduled_requests_by_request_group_id()
        # Ensure both requests can get scheduled, but request 4 is after the RR reservation in its window
        assert_true(4 in scheduled_rgs)
        assert_true(4 in scheduled_rgs[4])
        assert_true(3 in scheduled_rgs[4])
        semester_start = scheduler_start - timedelta(days=150)
        dt_start, dt_end = get_reservation_datetimes(scheduled_rgs[4][4],
                                                     semester_start)
        assert_equal(
            dt_start,
            self.base_time + timedelta(hours=1, minutes=0, seconds=30))
        assert_equal(
            dt_end,
            self.base_time + timedelta(hours=1, minutes=0, seconds=30) +
            timedelta(seconds=1750))

    def test_normal_requests_can_schedule_after_running_rr(self):
        ''' Verifies that a normal request will respect a already running RR whose time overlaps with it's window.
            Ensures that the normal request starts after the end of the RR.
        '''
        rr_request_group_id = 99
        running_request_group = create_running_request_group(
            request_group_id=rr_request_group_id,
            request_id=99,
            resource=self.resource_3,
            start=self.base_time,
            end=self.base_time + timedelta(hours=1, minutes=0, seconds=30))
        scheduler_start = self.base_time - timedelta(hours=10)
        result = self._schedule_requests([
            self.rr_request_group_1,
        ], [
            self.many_request_group_2,
        ],
                                         scheduler_start,
                                         rr_loop=False,
                                         block_schedule_by_resource={},
                                         running_request_groups=[
                                             running_request_group,
                                         ],
                                         rapid_response_ids=[
                                             rr_request_group_id,
                                         ])
        scheduled_rgs = result.get_scheduled_requests_by_request_group_id()
        # Ensure request 4 is after the RR running request in its window, and request 3 is blocked by the running RR
        assert_true(4 in scheduled_rgs)
        assert_true(4 in scheduled_rgs[4])
        assert_false(3 in scheduled_rgs[4])
        semester_start = scheduler_start - timedelta(days=150)
        dt_start, dt_end = get_reservation_datetimes(scheduled_rgs[4][4],
                                                     semester_start)
        assert_equal(
            dt_start,
            self.base_time + timedelta(hours=1, minutes=0, seconds=30))
        assert_equal(
            dt_end,
            self.base_time + timedelta(hours=1, minutes=0, seconds=30) +
            timedelta(seconds=1750))

    def test_one_rr_has_correct_cancel_date_list(self):
        ''' Schedules a single RR and verifies it's time appears in the cancellation date list on the resource
        '''
        scheduler_start = self.base_time - timedelta(hours=10)
        result = self._schedule_requests([
            self.rr_request_group_1,
        ], [
            self.many_request_group_2,
        ],
                                         scheduler_start,
                                         rr_loop=True,
                                         block_schedule_by_resource={})

        scheduled_rgs = result.get_scheduled_requests_by_request_group_id()
        assert_true(5 in scheduled_rgs)
        assert_true(5 in scheduled_rgs[5])

        semester_start = scheduler_start - timedelta(days=150)
        dt_start, dt_end = get_reservation_datetimes(scheduled_rgs[5][5],
                                                     semester_start)
        scheduler_runner = SchedulerRunner(SchedulerParameters(dry_run=True),
                                           Mock(), Mock(), Mock(), Mock())
        scheduler_runner.semester_details = {
            'id': '2015A',
            'start': semester_start,
            'end': scheduler_start + timedelta(days=150)
        }
        cancel_date_list_by_resource = scheduler_runner._determine_schedule_cancelation_list_from_new_schedule(
            result.schedule)

        assert_true('1m0a.doma.ogg' in cancel_date_list_by_resource)
        assert_equal(len(cancel_date_list_by_resource['1m0a.doma.ogg']), 1)
        assert_equal(cancel_date_list_by_resource['1m0a.doma.ogg'][0][0],
                     dt_start)
        assert_equal(cancel_date_list_by_resource['1m0a.doma.ogg'][0][1],
                     dt_end)

    def test_multiple_rr_has_correct_cancel_date_list(self):
        ''' Schedules three nearly back to back RRs. Checks that each of their scheduled time appears in the date list
            for the resource they are scheduled in when getting dates to cancel.
        '''
        scheduler_start = self.base_time - timedelta(hours=10)
        result = self._schedule_requests(
            [self.rr_request_group_2, self.rr_request_group_1], [
                self.many_request_group_2,
            ],
            scheduler_start,
            rr_loop=True,
            block_schedule_by_resource={})

        scheduled_rgs = result.get_scheduled_requests_by_request_group_id()
        assert_true(5 in scheduled_rgs)
        assert_true(5 in scheduled_rgs[5])
        assert_true(6 in scheduled_rgs)
        assert_true(3 in scheduled_rgs[6])
        assert_true(1 in scheduled_rgs[6])

        semester_start = scheduler_start - timedelta(days=150)
        scheduler_runner = SchedulerRunner(SchedulerParameters(dry_run=True),
                                           Mock(), Mock(), Mock(), Mock())
        scheduler_runner.semester_details = {
            'id': '2015A',
            'start': semester_start,
            'end': scheduler_start + timedelta(days=150)
        }
        cancel_date_list_by_resource = scheduler_runner._determine_schedule_cancelation_list_from_new_schedule(
            result.schedule)
        assert_true('1m0a.doma.ogg' in cancel_date_list_by_resource)
        assert_equal(len(cancel_date_list_by_resource['1m0a.doma.ogg']), 3)
        assert_equal(len(cancel_date_list_by_resource['1m0a.doma.ogg']), 3)

        dt_start, dt_end = get_reservation_datetimes(scheduled_rgs[6][1],
                                                     semester_start)
        assert_equal(cancel_date_list_by_resource['1m0a.doma.ogg'][0][0],
                     dt_start)
        assert_equal(cancel_date_list_by_resource['1m0a.doma.ogg'][0][1],
                     dt_end)

        dt_start, dt_end = get_reservation_datetimes(scheduled_rgs[6][3],
                                                     semester_start)
        assert_equal(cancel_date_list_by_resource['1m0a.doma.ogg'][1][0],
                     dt_start)
        assert_equal(cancel_date_list_by_resource['1m0a.doma.ogg'][1][1],
                     dt_end)

        dt_start, dt_end = get_reservation_datetimes(scheduled_rgs[5][5],
                                                     semester_start)
        assert_equal(cancel_date_list_by_resource['1m0a.doma.ogg'][2][0],
                     dt_start)
        assert_equal(cancel_date_list_by_resource['1m0a.doma.ogg'][2][1],
                     dt_end)