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
예제 #2
0
    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
예제 #3
0
    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
예제 #4
0
    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