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
def _visible_targets(self, instrument_type, windows): '''Return a list of targets that are currently visible in the window''' site_details = configdb.get_sites_with_instrument_type_and_location( instrument_type) visible_targets = [] for target in TARGET_LIST: intervals_by_site = {} for site in site_details: rise_set_site = get_rise_set_site(site_details[site]) rise_set_target = get_rise_set_target(target) intervals_by_site[site] = [] for window in windows: visibility = get_rise_set_visibility( rise_set_site, window['start'], window['end'], site_details[site]) try: intervals_by_site[site].extend( visibility.get_observable_intervals( rise_set_target, airmass=2.0, moon_distance=Angle(degrees=0.0))) except MovingViolation: pass largest_interval = get_largest_interval(intervals_by_site) if largest_interval > timedelta(minutes=15): visible_targets.append(target) return visible_targets
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
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
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
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()