def set_tmin_for_next_pointing(tmin, duration, array='South', sunzenith=105.0, moonzenith=90.0, skip_days=6.0): """ Set start time for next pointing Adds duration and 2 min for slew, and takes care of day and night. It is assumed that the night lasts from [0,10] and the day lasts from [10,24] Parameters ---------- tmin : float Start time of current pointing (sec) duration : float Duration of current pointing (sec) array : str, optional Array sunzenith : float, optional Sun zenith angle constraint (deg) moonzenith : float, optional Moon zenith angle constraint (deg) skip_days : float, optional Number of days to skip after Sun rise Returns ------- tmin : float Start time of new pointing """ # Set time tref = gammalib.GTimeReference(51544.5, 's', 'TT', 'LOCAL') time = gammalib.GTime(tmin, tref) # Add duration and 2 min for slew time += duration + 120.0 # If the Sun or the Moon is above zenith angle limit then increment # time until the Sun and the Moon is again below the limit add_days = skip_days while True: sun = zenith_sun(time, array=array) moon = zenith_moon(time, array=array) if sun < sunzenith and add_days > 0.0: time += 86400.0 * add_days add_days = 0.0 if sun < sunzenith or moon < moonzenith: time += 240.0 # Add 4 min and check again else: break # Convert back to seconds tmin = time.convert(tref) # Return start time return tmin
def _get_parameters(self): """ Get parameters from parfile and setup observations """ # If there are no observations in container then query the inobs # parameter if self.obs().is_empty(): self['inobs'].filename() # Query relevant pointing selection parameters pntselect = self['pntselect'].string() coordsys = self['coordsys'].string() if coordsys == 'CEL': self['ra'].real() self['dec'].real() else: self['glon'].real() self['glat'].real() if pntselect == 'CIRCLE': self['rad'].real() else: self['width'].real() self['height'].real() # Query time interval. The GammaLib time reference is specified as a # dummy argument since the relevant intervals will be queried later # using the time reference for each observation self._get_gti(gammalib.GTimeReference()) # Query ahead output model filename if self._read_ahead(): self['outobs'].filename() # If there are no observations in container then get them from the # parameter file if self.obs().is_empty(): self.obs(self._get_observations(False)) # Write input parameters into logger self._log_parameters(gammalib.TERSE) # Return return
def _hour_angle_weight(self): """ Compute hour angle weights Computes an array specifying how many hours the array was observing for a given hour angle during the time interval [tmin,tmax]. The hour angle runs from 0 to 360 degrees. Compute during which time the array was observing due to hour angle constraints. We take here a very simple model where the night lasts between 6 and 10 hours (or 90-150 degrees), with a sinusoidal variation along the year. Every day the hour angle of the Sun set is advancing by 4 minutes. """ # Write header self._log_header2(gammalib.NORMAL, 'Hour angle weights') # Get array geographic longitude #geolon = self['geolon'].real() # Get MET time reference tref = gammalib.GTimeReference(self['mjdref'].real(),'s','TT','LOCAL') # Get time interval tmin = self['tmin'].time(tref) tmax = self['tmax'].time(tref) # Initialise hour angle list hour_angles = [] # Set number of hour angle bins and compute conversion factor and weight nsteps = 1000 ra2inx = float(nsteps)/360.0 # Conversion from RA (deg) to index weight = 24.0/float(nsteps) # Weight per hour angle step # Initialise hour angle array and weight hours = [0.0 for i in range(nsteps)] # Initialise time and loop until the end time is reached time = tmin while time <= tmax: # Compute Right Ascension and Declination of Sun in degrees sunra, sundec = self._sun_radec(time) # Compute by how much the zenith angle map needs to be shifted # to correspond to the actual time # # NOTE: The local apparent siderial time only is relevant if # the time interval is shorter than a day. Only in that case # we have to set the corresponding hour angles to zero. We need # to think how to properly implement that. #last = time.last(geolon) * 15.0 #print(last) # Compute the time from now when the Sun will culminate. The time # is here expressed in degrees dra_sun = self._sun_ra_exclusion(time) # Set [0,ra_start] and [ra_stop,360.0] ra_start = sunra - dra_sun ra_stop = sunra + dra_sun # Case 1: The RA interval during which the Sun is above the zenith # angle constraint is fully comprised within the [0,360] interval. # In that case the dark time is comprised of two intervals: # [0,ra_start] and [ra_stop,360] if ra_start >= 0.0 and ra_stop <= 360.0: inx_stop = int(ra_start * ra2inx + 0.5) inx_start = int(ra_stop * ra2inx + 0.5) for i in range(0,inx_stop): hours[i] += weight for i in range(inx_start,nsteps): hours[i] += weight # Log setting logs = 'Sun=(%.3f,%.3f) dRA=%.3f RA_excl=[%.3f,%.3f] '\ 'Indices=[0-%d] & [%d-%d] Dark time=%.2f h %s' % \ (sunra, sundec, dra_sun, ra_start, ra_stop, inx_stop, inx_start, nsteps, float(inx_stop+(nsteps-inx_start))*weight, '(Case 1)') self._log_value(gammalib.VERBOSE, time.utc(), logs) # Case 2: The RA interval during which the Sun is above the zenith # angle constraint is starting at negative RA. In that case the # dark time is comprised of a single interval # [ra_stop,ra_start+360] elif ra_start < 0.0: inx_start = int(ra_stop * ra2inx + 0.5) inx_stop = int((ra_start+360.0) * ra2inx + 0.5) for i in range(inx_start,inx_stop): hours[i] += weight # Log setting logs = 'Sun=(%.3f,%.3f) dRA=%.3f RA_excl=[%.3f,%.3f] '\ 'Indices=[%d-%d] Dark time=%.2f h %s' % \ (sunra, sundec, dra_sun, ra_start, ra_stop, inx_start, inx_stop, float(inx_stop-inx_start)*weight, '(Case 2)') self._log_value(gammalib.VERBOSE, time.utc(), logs) # Case 3: The RA interval during which the Sun is above the zenith # angle constraint is stopping at RA>360. In that case the dark # time is comprised of a single interval [ra_stop-360,ra_start] elif ra_stop > 360.0: inx_start = int((ra_stop-360.0) * ra2inx + 0.5) inx_stop = int(ra_start * ra2inx + 0.5) for i in range(inx_start,inx_stop): hours[i] += weight # Log setting logs = 'Sun=(%.3f,%.3f) dRA=%.3f RA_excl=[%.3f,%.3f] '\ 'Indices=[%d-%d] Dark time=%.2f h %s' % \ (sunra, sundec, dra_sun, ra_start, ra_stop, inx_start, inx_stop, float(inx_stop-inx_start)*weight, '(Case 3)') self._log_value(gammalib.VERBOSE, time.utc(), logs) # Add seconds of one day and start with next day time += 86400.0 # Build hour angle list dh = 360.0/float(nsteps) for i in range(nsteps): hour_angle = {'angle': dh*float(i), 'hours': hours[i]} hour_angles.append(hour_angle) # Log hour angle weights total = 0.0 for hour_angle in hour_angles: total += hour_angle['hours'] self._log_value(gammalib.EXPLICIT, 'Observing time', str(total)+' h') self._log_value(gammalib.EXPLICIT, 'Number of hour angle bins', len(hour_angles)) # Return hour angle weights return hour_angles
def run(self): """ Run the script Raises ------ RuntimeError Invalid pointing definition file format """ # Switch screen logging on in debug mode if self._logDebug(): self._log.cout(True) # Get parameters self._get_parameters() # Write header into logger self._log_header1(gammalib.TERSE, 'Creating observation definition XML file') # Load pointing definition file if it is not already set. Extract # the number of columns and pointings if self._pntdef.size() == 0: self._pntdef = gammalib.GCsv(self['inpnt'].filename(), ',') ncols = self._pntdef.ncols() npnt = self._pntdef.nrows() - 1 # Raise an exception if there is no header information if self._pntdef.nrows() < 1: raise RuntimeError('No header found in pointing definition file.') # Clear observation container self._obs.clear() # Initialise observation identifier counter identifier = 1 # Extract header columns from pointing definition file and put them # into a list header = [] for col in range(ncols): header.append(self._pntdef[0, col]) # Loop over all pointings for pnt in range(npnt): # Set pointing definition CSV file row index row = pnt + 1 # Create empty CTA observation obs = gammalib.GCTAObservation() # Set observation name. If no observation name was given then # use "None". if 'name' in header: name = self._pntdef[row, header.index('name')] else: name = self['name'].string() obs.name(name) # Set observation identifier. If no observation identified was # given the use the internal counter. if 'id' in header: obsid = self._pntdef[row, header.index('id')] else: obsid = '%6.6d' % identifier identifier += 1 obs.id(obsid) # Set pointing. Either use "ra" and "dec" or "lon" and "lat". # If none of these pairs are given then raise an exception. if 'ra' in header and 'dec' in header: ra = float(self._pntdef[row, header.index('ra')]) dec = float(self._pntdef[row, header.index('dec')]) pntdir = gammalib.GSkyDir() pntdir.radec_deg(ra, dec) elif 'lon' in header and 'lat' in header: lon = float(self._pntdef[row, header.index('lon')]) lat = float(self._pntdef[row, header.index('lat')]) pntdir = gammalib.GSkyDir() pntdir.lb_deg(lon, lat) else: raise RuntimeError('No (ra,dec) or (lon,lat) columns ' 'found in pointing definition file.') obs.pointing(gammalib.GCTAPointing(pntdir)) # Set response function. If no "caldb" or "irf" information is # provided then use the user parameter values. if 'caldb' in header: caldb = self._pntdef[row, header.index('caldb')] else: caldb = self['caldb'].string() if 'irf' in header: irf = self._pntdef[row, header.index('irf')] else: irf = self['irf'].string() if caldb != '' and irf != '': obs = self._set_irf(obs, caldb, irf) # Set deadtime correction factor. If no information is provided # then use the user parameter value "deadc". if 'deadc' in header: deadc = float(self._pntdef[row, header.index('deadc')]) else: deadc = self['deadc'].real() obs.deadc(deadc) # Set Good Time Interval. If no information is provided then use # the user parameter values "tmin" and "duration". if 'tmin' in header: self._tmin = float(self._pntdef[row, header.index('tmin')]) if 'duration' in header: duration = float(self._pntdef[row, header.index('duration')]) else: duration = self['duration'].real() tref = gammalib.GTimeReference(self['mjdref'].real(), 's') tmin = self._tmin tmax = self._tmin + duration gti = gammalib.GGti(tref) tstart = gammalib.GTime(tmin, tref) tstop = gammalib.GTime(tmax, tref) self._tmin = tmax gti.append(tstart, tstop) obs.ontime(gti.ontime()) obs.livetime(gti.ontime() * deadc) # Set Energy Boundaries. If no "emin" or "emax" information is # provided then use the user parameter values in case they are # valid. has_emin = False has_emax = False if 'emin' in header: emin = float(self._pntdef[row, header.index('emin')]) has_emin = True else: if self['emin'].is_valid(): emin = self['emin'].real() has_emin = True if 'emax' in header: emax = float(self._pntdef[row, header.index('emax')]) has_emax = True else: if self['emax'].is_valid(): emax = self['emax'].real() has_emax = True has_ebounds = has_emin and has_emax if has_ebounds: ebounds = gammalib.GEbounds(gammalib.GEnergy(emin, 'TeV'), gammalib.GEnergy(emax, 'TeV')) # Set ROI. If no ROI radius is provided then use the user # parameters "rad". has_roi = False if 'rad' in header: rad = float(self._pntdef[row, header.index('rad')]) has_roi = True else: if self['rad'].is_valid(): rad = self['rad'].real() has_roi = True if has_roi: roi = gammalib.GCTARoi(gammalib.GCTAInstDir(pntdir), rad) # Create an empty event list event_list = gammalib.GCTAEventList() event_list.gti(gti) # If available, set the energy boundaries and the ROI if has_ebounds: event_list.ebounds(ebounds) if has_roi: event_list.roi(roi) # Attach event list to CTA observation obs.events(event_list) # Write observation into logger name = obs.instrument() + ' observation' value = 'Name="%s" ID="%s"' % (obs.name(), obs.id()) self._log_value(gammalib.NORMAL, name, value) self._log_string(gammalib.EXPLICIT, str(obs) + '\n') # Append observation self._obs.append(obs) # Return return
def schedule_observations_v3(obsdef, tstart, dl_min=1.0, dl_max=5.0, array='South', skip_days=6.0, dzenith_max=5.0): """ Schedule observations Parameters ---------- obsdef : list of dict Observation definition tstart : float Start time (s) dl_min : float, optional Minimum longitude step (deg) dl_max : float, optional Maximum longitude step (deg) array : string Array site ('South' or 'North') skip_days : float, optional Number of days to skip after Sun rise Returns ------- obsdef : list of dict Scheduled observation definition """ # Initialise statistics nobs = len(obsdef) last_lon = 1000.0 last_lat = 0.0 lon_min_step = dl_min lon_max_step = dl_max n_blocked = 0 # Initialise time time = set_tmin_for_next_pointing(tstart, 0.0, array=array, skip_days=skip_days) # Initialise scheduled observation definitions scheduled_obsdef = [] # Schedule observations until all are treated while len(scheduled_obsdef) < nobs: # Find next valid observation with smallest zenith angle max_zenith = 0.0 min_zenith = 180.0 min_dzenith = 180.0 for obs in obsdef: # Consider next non-scheduled observation if not obs['scheduled']: # If latitude is comparable then skip observation. This leads # to a toggle between the positive and negative latitudes of # the two-row pattern if abs(obs['lat'] - last_lat) < 0.1: continue # If longitude difference is smaller than minimum longitude # step then skip observation if last_lon != 1000.0: if obs['lon'] - last_lon < lon_min_step: continue # If longitude difference is larger than maximum longitude # step the skip step if last_lon != 1000.0: if obs['lon'] - last_lon > lon_max_step: continue # Compute zenith angle dir = gammalib.GSkyDir() dir.lb_deg(obs['lon'], obs['lat']) tref = gammalib.GTimeReference(51544.5, 's', 'TT', 'LOCAL') gtime = gammalib.GTime(time, tref) zenith = zenith_dir(gtime, dir, array=array) # Compute zenith distance from culmination dzenith = zenith - obs['best_zenith'] # If zenith distance from culmination is worse by dzenith_max # deg then skip pointing if dzenith > dzenith_max: continue # Keep pointing with smallest distance from culmination if dzenith < min_dzenith: min_dzenith = dzenith min_zenith = zenith max_zenith = obs['maxzenith'] min_obs = obs # If an observation with an acceptable zenith angle was found then # use it if min_dzenith < dzenith_max: # Set time and duration min_obs['tmin'] = time # Set zenith angle min_obs['zenith'] = min_zenith # Assign IRF zenith if min_zenith < 30.0: irfz = 20 elif min_zenith < 50.0: irfz = 40 else: irfz = 60 irf = '{}_z{}_50h'.format(array, irfz) # Set IRF information min_obs['irf'] = irf # Schedule observation scheduled_obsdef.append(min_obs) # Update last pointing last_lon = min_obs['lon'] last_lat = min_obs['lat'] # Dump gtime = gammalib.GTime(time, tref) print('%8.3f %8.3f %8.3f %4d/%4d %s %s' % (min_obs['lon'], min_obs['lat'], min_zenith, len(scheduled_obsdef), nobs, gtime.utc(), min_obs['name'])) # Signal that observation was scheduled min_obs['scheduled'] = True # ... otherwise remove the longitude and latitude constraints else: # Remove constraints last_lon = 1000.0 last_lat = 0.0 # Log number of blocks n_blocked += 1 # If blocked for more than 5000 iterations signal if n_blocked > 5000: print('WARNING: Scheduling blocked') for obs in obsdef: if not obs['scheduled']: print('- %s %.2f %.2f %.2f %.2f' % (obs['name'], obs['lon'], obs['lat'], obs['best_zenith'], obs['maxzenith'])) break # Increment time for next pointing time = set_tmin_for_next_pointing(time, obs_time * 3600.0, array=array, skip_days=skip_days) # Return scheduled observation return scheduled_obsdef