Example #1
0
def expand_cadence_request(request_dict, is_staff=False):
    '''
    Takes in a valid cadence request (valid request with cadence block), and expands the request into a list of requests
    with their windows determined by the cadence parameters. Only valid requests that pass rise-set are returned, with
    failing requests silently left out of the returned list.
    :param request_dict: a valid request dictionary with cadence information.
    :return: Expanded list of requests with valid windows within the cadence.
    '''
    cadence = request_dict['cadence']
    # now expand the request into a list of requests with the proper windows from the cadence block
    cadence_requests = []
    half_jitter = timedelta(hours=cadence['jitter'] / 2.0)
    request_duration = get_request_duration(request_dict)
    request_window_start = cadence['start']

    while request_window_start < cadence['end']:
        window_start = max(request_window_start - half_jitter, cadence['start'])
        window_end = min(request_window_start + half_jitter, cadence['end'])

        # test the rise_set of this window
        request_dict['windows'] = [{'start': window_start, 'end': window_end}]
        intervals_by_site = get_filtered_rise_set_intervals_by_site(request_dict, is_staff=is_staff)
        largest_interval = get_largest_interval(intervals_by_site)
        if largest_interval.total_seconds() > request_duration and window_end > timezone.now():
            # this cadence window passes rise_set and is in the future so add it to the list
            request_copy = request_dict.copy()
            del request_copy['cadence']
            cadence_requests.append(request_copy)

        request_window_start += timedelta(hours=cadence['period'])
    return cadence_requests
Example #2
0
def get_telescope_states_for_request(request_dict, is_staff=False):
    # TODO: update to support multiple instruments in a list
    instrument_type = request_dict['configurations'][0]['instrument_type']
    site_intervals = {}
    only_schedulable = not (is_staff and ConfigDB.is_location_fully_set(
        request_dict.get('location', {})))
    # Build up the list of telescopes and their rise set intervals for the target on this request
    site_data = configdb.get_sites_with_instrument_type_and_location(
        instrument_type=instrument_type,
        site_code=request_dict['location']['site']
        if 'site' in request_dict['location'] else '',
        enclosure_code=request_dict['location']['enclosure']
        if 'enclosure' in request_dict['location'] else '',
        telescope_code=request_dict['location']['telescope']
        if 'telescope' in request_dict['location'] else '',
        only_schedulable=only_schedulable)

    for site, details in site_data.items():
        if site not in site_intervals:
            site_intervals[site] = get_filtered_rise_set_intervals_by_site(
                request_dict, site=site, is_staff=is_staff).get(site, [])

    # If you have no sites, return the empty dict here
    if not site_intervals:
        return {}

    # Retrieve the telescope states for that set of sites
    min_window_time = min(
        [window['start'] for window in request_dict['windows']])
    max_window_time = max(
        [window['end'] for window in request_dict['windows']])
    telescope_states = TelescopeStates(
        start=min_window_time,
        end=max_window_time,
        sites=list(site_intervals.keys()),
        instrument_types=[instrument_type],
        location_dict=request_dict.get('location', {}),
        only_schedulable=only_schedulable).get()
    # Remove the empty intervals from the dictionary
    site_intervals = {
        site: intervals
        for site, intervals in site_intervals.items() if intervals
    }

    # Filter the telescope states list with the site intervals
    filtered_telescope_states = filter_telescope_states_by_intervals(
        telescope_states, site_intervals, min_window_time, max_window_time)

    return filtered_telescope_states
Example #3
0
 def _visible_intervals(self, request):
     visible_intervals = {}
     for site in self.sites:
         if not request.location.site or request.location.site == site[
                 'code']:
             intervals = get_filtered_rise_set_intervals_by_site(
                 request.as_dict(), site['code']).get(site['code'], [])
             for r, s in intervals:
                 effective_rise = max(r, self.now)
                 if s > self.now and (
                         s - effective_rise).seconds >= request.duration:
                     if site['code'] in visible_intervals:
                         visible_intervals[site['code']].append(
                             (effective_rise, s))
                     else:
                         visible_intervals[site['code']] = [(effective_rise,
                                                             s)]
     return visible_intervals
def get_request_duration_dict(request_dict, is_staff=False):
    req_durations = {'requests': []}
    for req in request_dict:
        req_info = {'duration': get_request_duration(req)}
        conf_durations = [
            get_configuration_duration(conf) for conf in req['configurations']
        ]
        req_info['configurations'] = conf_durations
        rise_set_intervals = get_filtered_rise_set_intervals_by_site(
            req, is_staff=is_staff)
        req_info['largest_interval'] = get_largest_interval(
            rise_set_intervals).total_seconds()
        req_info['largest_interval'] -= (PER_CONFIGURATION_STARTUP_TIME +
                                         PER_CONFIGURATION_GAP)
        req_durations['requests'].append(req_info)
    req_durations['duration'] = sum(
        [req['duration'] for req in req_durations['requests']])

    return req_durations
Example #5
0
def get_request_duration_dict(request_dict, is_staff=False):
    req_durations = {'requests': []}
    for req in request_dict:
        req_info = {'duration': get_total_request_duration(req)}
        conf_durations = []
        for conf in req['configurations']:
            request_overheads = configdb.get_request_overheads(
                conf['instrument_type'])
            conf_durations.append(
                get_configuration_duration(conf, request_overheads))
        req_info['configurations'] = conf_durations
        rise_set_intervals = get_filtered_rise_set_intervals_by_site(
            req, is_staff=is_staff)
        req_info['largest_interval'] = get_largest_interval(
            rise_set_intervals).total_seconds()
        req_durations['requests'].append(req_info)
    req_durations['duration'] = sum(
        [req['duration'] for req in req_durations['requests']])

    return req_durations
Example #6
0
def get_airmasses_for_request_at_sites(request_dict, is_staff=False):
    # TODO: Change to work with multiple instrument types and multiple constraints and multiple targets
    data = {'airmass_data': {}}
    instrument_type = request_dict['configurations'][0]['instrument_type']
    constraints = request_dict['configurations'][0]['constraints']
    target = request_dict['configurations'][0]['target']
    target_type = str(target.get('type', '')).upper()
    only_schedulable = not (is_staff and ConfigDB.is_location_fully_set(request_dict.get('location', {})))

    if target_type in ['ICRS', 'ORBITAL_ELEMENTS'] and TARGET_TYPE_HELPER_MAP[target_type](target).is_valid():
        site_data = configdb.get_sites_with_instrument_type_and_location(
            instrument_type=instrument_type,
            site_code=request_dict['location'].get('site'),
            enclosure_code=request_dict['location'].get('enclosure'),
            telescope_code=request_dict['location'].get('telescope'),
            only_schedulable=only_schedulable
        )
        rs_target = get_rise_set_target(target)
        for site_id, site_details in site_data.items():
            night_times = []
            site_lat = Angle(degrees=site_details['latitude'])
            site_lon = Angle(degrees=site_details['longitude'])
            site_alt = site_details['altitude']
            intervals = get_filtered_rise_set_intervals_by_site(request_dict, site_id, is_staff=is_staff).get(site_id, [])
            for interval in intervals:
                night_times.extend(
                    [time for time in date_range_from_interval(interval[0], interval[1], dt=timedelta(minutes=10))])

            if len(night_times) > 0:
                if site_id not in data:
                    data['airmass_data'][site_id] = {}
                data['airmass_data'][site_id]['times'] = [time.strftime('%Y-%m-%dT%H:%M') for time in night_times]
                data['airmass_data'][site_id]['airmasses'] = calculate_airmass_at_times(
                    night_times, rs_target, site_lat, site_lon, site_alt
                )
                data['airmass_limit'] = constraints['max_airmass']

    return data
Example #7
0
def get_airmasses_for_request_at_sites(request_dict, is_staff=False):
    data = {
        'airmass_data': {},
    }
    instrument_type = request_dict['configurations'][0]['instrument_type']
    constraints = request_dict['configurations'][0]['constraints']
    target = request_dict['configurations'][0]['target']
    target_type = str(target.get('type', '')).upper()
    only_schedulable = not (is_staff and ConfigDB.is_location_fully_set(
        request_dict.get('location', {})))

    if target_type in [
            'ICRS', 'ORBITAL_ELEMENTS'
    ] and TARGET_TYPE_HELPER_MAP[target_type](target).is_valid():
        site_data = configdb.get_sites_with_instrument_type_and_location(
            instrument_type=instrument_type,
            site_code=request_dict['location'].get('site'),
            enclosure_code=request_dict['location'].get('enclosure'),
            telescope_code=request_dict['location'].get('telescope'),
            only_schedulable=only_schedulable)
        rs_target = get_rise_set_target(target)
        for site_id, site_details in site_data.items():
            night_times = []
            site_lat = Angle(degrees=site_details['latitude'])
            site_lon = Angle(degrees=site_details['longitude'])
            site_alt = site_details['altitude']
            intervals = get_filtered_rise_set_intervals_by_site(
                request_dict, site_id, is_staff=is_staff).get(site_id, [])
            for interval in intervals:
                night_times.extend([
                    time for time in date_range_from_interval(
                        interval[0], interval[1], dt=timedelta(minutes=10))
                ])

            if len(night_times) > 0:
                if site_id not in data:
                    data['airmass_data'][site_id] = {
                        'times': [
                            time.strftime('%Y-%m-%dT%H:%M')
                            for time in night_times
                        ],
                    }

                # Need to average airmass values for set of unique targets in request
                unique_targets_constraints = set([
                    json.dumps((configuration['target'],
                                configuration['constraints']))
                    for configuration in request_dict['configurations']
                ])
                unique_count = len(unique_targets_constraints)
                max_airmass = 0.0
                for target_constraints in unique_targets_constraints:
                    (target, constraints) = json.loads(target_constraints)
                    rs_target = get_rise_set_target(target)
                    airmasses = calculate_airmass_at_times(
                        night_times, rs_target, site_lat, site_lon, site_alt)
                    if 'airmasses' in data['airmass_data'][site_id]:
                        for index, airmass_value in enumerate(airmasses):
                            data['airmass_data'][site_id]['airmasses'][
                                index] += airmass_value
                    else:
                        data['airmass_data'][site_id]['airmasses'] = airmasses
                    max_airmass += constraints['max_airmass']
                # Now we need to divide out the number of unique constraints/targets
                data['airmass_limit'] = max_airmass / unique_count
                data['airmass_data'][site_id]['airmasses'] = [
                    val / unique_count
                    for val in data['airmass_data'][site_id]['airmasses']
                ]

    return data
Example #8
0
    def validate(self, data):
        is_staff = False
        only_schedulable = True
        request_context = self.context.get('request')
        if request_context:
            is_staff = request_context.user.is_staff
            only_schedulable = not (is_staff and ConfigDB.is_location_fully_set(data.get('location', {})))
        # check if the instrument specified is allowed
        # TODO: Check if ALL instruments are available at a resource defined by location
        if 'location' in data:
            # Check if the location is fully specified, and if not then use only schedulable instruments
            valid_instruments = configdb.get_instrument_types(data.get('location', {}),
                                                              only_schedulable=only_schedulable)
            for configuration in data['configurations']:
                if configuration['instrument_type'] not in valid_instruments:
                    msg = _("Invalid instrument type '{}' at site={}, enc={}, tel={}. \n").format(
                        configuration['instrument_type'],
                        data.get('location', {}).get('site', 'Any'),
                        data.get('location', {}).get('enclosure', 'Any'),
                        data.get('location', {}).get('telescope', 'Any')
                    )
                    msg += _("Valid instruments include: ")
                    for inst_name in valid_instruments:
                        msg += inst_name + ', '
                    msg += '.'
                    if is_staff and not only_schedulable:
                        msg += '\nStaff users must fully specify location to schedule on non-SCHEDULABLE instruments'
                    raise serializers.ValidationError(msg)

        if 'acceptability_threshold' not in data:
            data['acceptability_threshold'] = max(
                [configdb.get_default_acceptability_threshold(configuration['instrument_type'])
                 for configuration in data['configurations']]
            )

        # check that the requests window has enough rise_set visible time to accomodate the requests duration
        if data.get('windows'):
            duration = get_request_duration(data)
            rise_set_intervals_by_site = get_filtered_rise_set_intervals_by_site(data, is_staff=is_staff)
            largest_interval = get_largest_interval(rise_set_intervals_by_site)
            for configuration in data['configurations']:
                if 'REPEAT' in configuration['type'].upper() and configuration.get('fill_window'):
                    max_configuration_duration = largest_interval.total_seconds() - duration + configuration.get('repeat_duration', 0) - 1
                    configuration['repeat_duration'] = max_configuration_duration
                    duration = get_request_duration(data)

                # delete the fill window attribute, it is only used for this validation
                try:
                    del configuration['fill_window']
                except KeyError:
                    pass
            if largest_interval.total_seconds() <= 0:
                raise serializers.ValidationError(
                    _(
                        'According to the constraints of the request, the target is never visible within the time '
                        'window. Check that the target is in the nighttime sky. Consider modifying the time '
                        'window or loosening the airmass or lunar separation constraints. If the target is '
                        'non sidereal, double check that the provided elements are correct.'
                    )
                )
            if largest_interval.total_seconds() <= duration:
                raise serializers.ValidationError(
                    (
                        'According to the constraints of the request, the target is visible for a maximum of {0:.2f} '
                        'hours within the time window. This is less than the duration of your request {1:.2f} hours. '
                        'Consider expanding the time window or loosening the airmass or lunar separation constraints.'
                    ).format(
                        largest_interval.total_seconds() / 3600.0,
                        duration / 3600.0
                    )
                )
        return data
Example #9
0
    def test_get_largest_rise_set_interval_only_uses_one_site(self):
        configdb_patcher = patch(
            'observation_portal.common.configdb.ConfigDB.get_sites_with_instrument_type_and_location'
        )
        mock_configdb = configdb_patcher.start()
        mock_configdb.return_value = {
            'tst': {
                'latitude': -30.1673833333,
                'longitude': -70.8047888889,
                'horizon': 15.0,
                'altitude': 100.0,
                'ha_limit_pos': 4.6,
                'ha_limit_neg': -4.6,
                'zenith_blind_spot': 0.0
            },
            'abc': {
                'latitude': -32.3805542,
                'longitude': 20.8101815,
                'horizon': 15.0,
                'altitude': 100.0,
                'ha_limit_pos': 4.6,
                'ha_limit_neg': -4.6,
                'zenith_blind_spot': 0.0
            }
        }
        configdb_patcher2 = patch(
            'observation_portal.common.configdb.ConfigDB.get_telescopes_with_instrument_type_and_location')
        mock_configdb2 = configdb_patcher2.start()
        mock_configdb2.return_value = {
            '1m0a.doma.tst': {
                'latitude': -30.1673833333,
                'longitude':-70.8047888889,
                'horizon': 15.0,
                'altitude': 100.0,
                'ha_limit_pos': 4.6,
                'ha_limit_neg': -4.6,
                'zenith_blind_spot': 0.0
            },
            '1m0a.doma.abc': {
                'latitude': -32.3805542,
                'longitude': 20.8101815,
                 'horizon': 15.0,
                 'altitude': 100.0,
                 'ha_limit_pos': 4.6,
                 'ha_limit_neg': -4.6,
                 'zenith_blind_spot': 0.0
            }
        }

        request_dict = {'location': {'telescope_class': '1m0'},
                        'windows': [
                            {
                                'start': datetime(2016, 9, 4),
                                'end': datetime(2016, 9, 5)
                            }
                        ],
                        'configurations': [
                            {
                                'instrument_type': '1M0-SCICAM-SINISTRO',
                                'instrument_configs': [
                                    {
                                        'exposure_time': 6000,
                                        'exposure_count': 5
                                    }
                                ],
                                'target': {
                                    'type': 'ICRS',
                                    'ra': 35.0,
                                    'dec': -53.0,
                                    'proper_motion_ra': 0.0,
                                    'proper_motion_dec': 0.0,
                                    'epoch': 2000,
                                    'parallax': 0.0
                                },
                                'constraints': {
                                    'max_airmass': 2.0,
                                    'min_lunar_distance': 30.0
                                }
                            }
                        ]}
        filtered_intervals = rise_set_utils.get_filtered_rise_set_intervals_by_site(request_dict)
        largest_interval = rise_set_utils.get_largest_interval(filtered_intervals)
        duration = timedelta(seconds=30000)
        self.assertGreater(duration, largest_interval)  # The duration is greater than the largest interval at a site

        combined_intervals = Intervals().union(
            [Intervals(timepoints=fi) for fi in filtered_intervals.values()]).toTupleList()
        largest_combined_interval = rise_set_utils.get_largest_interval({'tst': combined_intervals})
        self.assertLess(duration, largest_combined_interval)  # The duration is less then combined largest intervals

        configdb_patcher.stop()
        configdb_patcher2.stop()