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 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
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
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 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
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
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()