Commit 28cac9e0d87fd68e7517b144e39c9ceb7626b09d

Authored by Jürgen Knödlseder
1 parent b815cccb

Add initial csviscube version

cscripts/Makefile.am
... ... @@ -37,6 +37,7 @@ pythonscripts = $(builddir)/__init__.py \
37 37 $(srcdir)/cstsdist.py \
38 38 $(srcdir)/cstsmapmerge.py \
39 39 $(srcdir)/cstsmapsplit.py \
  40 + $(srcdir)/csviscube.py \
40 41 $(srcdir)/csworkflow.py \
41 42 $(srcdir)/csfindobs.py \
42 43 $(srcdir)/csiactcopy.py \
... ... @@ -63,6 +64,7 @@ parfiles = $(srcdir)/cscaldb.par \
63 64 $(srcdir)/cstsdist.par \
64 65 $(srcdir)/cstsmapmerge.par \
65 66 $(srcdir)/cstsmapsplit.par \
  67 + $(srcdir)/csviscube.par \
66 68 $(srcdir)/csworkflow.par \
67 69 $(srcdir)/csfindobs.par \
68 70 $(srcdir)/csiactcopy.par \
... ... @@ -86,6 +88,7 @@ linkscripts = cscaldb \
86 88 cstsdist \
87 89 cstsmapmerge \
88 90 cstsmapsplit \
  91 + csviscube \
89 92 csworkflow \
90 93 csfindobs \
91 94 csiactcopy \
... ... @@ -111,6 +114,7 @@ testscripts = $(srcdir)/tests/__init__.py \
111 114 $(top_srcdir)/test/test_cstsdist.py \
112 115 $(top_srcdir)/test/test_cstsmapmerge.py \
113 116 $(top_srcdir)/test/test_cstsmapsplit.py \
  117 + $(top_srcdir)/test/test_csviscube.py \
114 118 $(top_srcdir)/test/test_csworkflow.py \
115 119 $(top_srcdir)/test/test_csfindobs.py \
116 120 $(top_srcdir)/test/test_csiactcopy.py \
... ...
cscripts/__init__.py.in
... ... @@ -40,6 +40,7 @@ __all__ = [
40 40 "cstsdist",
41 41 "cstsmapmerge",
42 42 "cstsmapsplit",
  43 + "csviscube",
43 44 "csworkflow",
44 45 "obsutils",
45 46 "modutils",
... ... @@ -69,6 +70,7 @@ from cscripts.csspec import csspec
69 70 from cscripts.cstsdist import cstsdist
70 71 from cscripts.cstsmapmerge import cstsmapmerge
71 72 from cscripts.cstsmapsplit import cstsmapsplit
  73 +from cscripts.csviscube import csviscube
72 74 from cscripts.csworkflow import csworkflow
73 75 from cscripts import obsutils
74 76 from cscripts import modutils
... ...
cscripts/csviscube.par 0 → 100644
  1 +#############################################################################
  2 +# #
  3 +# CTA Science Analysis Tools #
  4 +# #
  5 +# ------------------------------------------------------------------------- #
  6 +# #
  7 +# File: csviscube.par #
  8 +# Version: 1.0.0 #
  9 +# #
  10 +# Author: Juergen Knoedlseder #
  11 +# Jurgen.Knodlseder@irap.omp.eu #
  12 +# IRAP #
  13 +# #
  14 +# Purpose: Parameter file for the csviscube script #
  15 +# #
  16 +#############################################################################
  17 +#
  18 +# General parameters
  19 +#===================
  20 +tmin, r, a, 0.0,,, "Start time (CTA MET in seconds)"
  21 +tmax, r, a, 31557600.0,,, "End time (CTA MET in seconds)"
  22 +geolon, r, a, 79.4041,,, "Geographic longitude of array (deg)"
  23 +geolat, r, a, -24.6272,,, "Geographic latitude of array (deg)"
  24 +sunzenith, r, h, 105.0,,, "Minimum Sun zenith angle for observations (deg)"
  25 +moonzenith, r, h, 90.0,,, "Minimum Moon zenith angle for observations (deg)"
  26 +outfile, f, a, viscube.fits,,, "Visibility cube output file"
  27 +
  28 +#
  29 +# Visibility cube parameters
  30 +#===========================
  31 +binsz, r, h, 1.0,,, "Image scale (in degrees/pixel)"
  32 +
  33 +#
  34 +# Standard parameters
  35 +#====================
  36 +chatter, i, h, 2,0,4, "Chattiness of output"
  37 +clobber, b, h, yes,,, "Overwrite existing output files with new output files?"
  38 +debug, b, h, no,,, "Debugging mode activated"
  39 +mode, s, h, ql,,, "Mode of automatic parameters"
  40 +logfile, f, h, csviscube.log,,, "Log filename"
... ...
cscripts/csviscube.py 0 → 100755
  1 +#! /usr/bin/env python
  2 +# ==========================================================================
  3 +# Compute a visibility cube
  4 +#
  5 +# Copyright (C) 2016 Juergen Knoedlseder
  6 +#
  7 +# This program is free software: you can redistribute it and/or modify
  8 +# it under the terms of the GNU General Public License as published by
  9 +# the Free Software Foundation, either version 3 of the License, or
  10 +# (at your option) any later version.
  11 +#
  12 +# This program is distributed in the hope that it will be useful,
  13 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
  14 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15 +# GNU General Public License for more details.
  16 +#
  17 +# You should have received a copy of the GNU General Public License
  18 +# along with this program. If not, see <http://www.gnu.org/licenses/>.
  19 +#
  20 +# ==========================================================================
  21 +import sys
  22 +import glob
  23 +import os
  24 +import math
  25 +import gammalib
  26 +import ctools
  27 +
  28 +
  29 +# =============== #
  30 +# csviscube class #
  31 +# =============== #
  32 +class csviscube(ctools.cscript):
  33 + """
  34 + Compute a visibility cube
  35 + """
  36 +
  37 + # Constructor
  38 + def __init__(self, *argv):
  39 + """
  40 + Constructor.
  41 +
  42 + Parameters
  43 + ----------
  44 + argv : list of str
  45 + List of IRAF command line parameter strings of the form
  46 + ``parameter=3``.
  47 +
  48 + Raises
  49 + ------
  50 + TypeError
  51 + An invalid number of command line arguments was provided.
  52 + """
  53 + # Set name
  54 + self._name = 'csviscube'
  55 + self._version = '1.0.0'
  56 +
  57 + # Initialise application by calling the appropriate class
  58 + # constructor.
  59 + self._init_cscript(argv)
  60 +
  61 + # Return
  62 + return
  63 +
  64 +
  65 + # Private methods
  66 + def _get_parameters(self):
  67 + """
  68 + Get all parameters
  69 + """
  70 + # Query parameters
  71 + self['tmin'].real()
  72 + self['tmax'].real()
  73 + self['geolon'].real()
  74 + self['geolat'].real()
  75 + self['sunzenith'].real()
  76 + self['moonzenith'].real()
  77 + self['outfile'].filename()
  78 + self['binsz'].real()
  79 +
  80 + # Write input parameters into logger
  81 + self._log_parameters(gammalib.TERSE)
  82 +
  83 + # Return
  84 + return
  85 +
  86 + def _hour_angle_weight(self):
  87 + """
  88 + Compute hour angle weight vector
  89 +
  90 + Compute during which time the array was observing due to hour angle
  91 + constraints. We take here a very simple model where the night lasts
  92 + between 6 and 10 hours (or 90-150 degrees), with a sinusoidal variation
  93 + along the year. Every day the hour angle of the Sun set is advancing by
  94 + 4 minutes.
  95 + """
  96 + # Set number of hour angle bins
  97 + nsteps = 1000
  98 + d_min = 6.0/24.0*nsteps
  99 + d_max = 10.0/24.0*nsteps
  100 +
  101 + # Initialise hour angle array and weight
  102 + hours = [0.0 for i in range(nsteps)]
  103 + weight = 24.0/float(nsteps) # Weight per hour angle step
  104 +
  105 + # Loop over all days in one year
  106 + for day in range(nsteps):
  107 + h_set = day
  108 + h_rise = day + \
  109 + int(d_min + (d_max-d_min)*math.sin(day/float(nsteps)*gammalib.pi))
  110 + for i in range(h_set,h_rise):
  111 + if i >= nsteps:
  112 + hours[i-nsteps] += weight
  113 + else:
  114 + hours[i] += weight
  115 +
  116 + # Return
  117 + return hours
  118 +
  119 + def _visibility_cube(self):
  120 + """
  121 + Compute visibility cube
  122 +
  123 + Compute visibility cube by displacing the zenith angle map for all
  124 + hour angles. The visibility cube contains the number of hours a given
  125 + celestial position is visible under a given zenith angle interval.
  126 + Summing over all zenith angle intervals specifies for how long a given
  127 + celestial position will be visible.
  128 + """
  129 + # Compute zenith angle map
  130 + map = self._zenith_angle_map()
  131 +
  132 + # Set zenith angle range and bin size
  133 + dz = 1.0
  134 + nz = int(60.0/dz)
  135 +
  136 + # Initialise visibility cube
  137 + self._cube = gammalib.GSkyMap('CAR','CEL',0.0,0.0,-1.0,1.0,360,180,nz)
  138 +
  139 + # Setup hour angle weights. They specify for how many hours a given
  140 + # hour angle will be observed during one year.
  141 + hours = self._hour_angle_weight()
  142 +
  143 + # Compute normalisation factor
  144 + norm = 365.0/float(len(hours))
  145 +
  146 + # Loop over all hour angle weights
  147 + for h, weight in enumerate(hours):
  148 +
  149 + # Compute temporal shift
  150 + shift = 360.0/float(len(hours)) * float(h)
  151 +
  152 + # Initialise shifted zenith angle map
  153 + map_shift = gammalib.GSkyMap('CAR','CEL',shift,0.0,-1.0,1.0,360,180)
  154 +
  155 + # Merge zenith angle map into shifted map
  156 + map_shift += map
  157 +
  158 + # Loop over all pixels of shifted map
  159 + for i, zenith in enumerate(map_shift):
  160 + iz = int(zenith/dz)
  161 + if iz < nz:
  162 + self._cube[i,iz] += weight * norm
  163 +
  164 + # Return
  165 + return
  166 +
  167 + def _zenith_angle_map(self):
  168 + """
  169 + Compute zenith angle map
  170 +
  171 + The zenith angle of a position (ra,dec) depends on the declination and
  172 + the hour angle h and is given by
  173 +
  174 + zenith(h,dec) = arccos( sin(lat) * sin(dec) + cos(lat) * cos(dec) * cos(h) )
  175 +
  176 + The hour angle h (or local hour angle, LHA) is defined as the difference
  177 + between local siderial time (LST) and the Right Ascension
  178 +
  179 + h = LST - ra
  180 +
  181 + LST is the difference between the Greenwich siderial time (GST) and the
  182 + geographic longitude of the observer
  183 +
  184 + LST = GST - lon
  185 +
  186 + where lon is counted positive west from the meridian. Hence
  187 +
  188 + h = GST - lon - ra
  189 +
  190 + The map is computed for h=-ra which is equivalent to GST=lon (or LST=0).
  191 + In other words, the map corresponds to the time when ra=0 passes the
  192 + local meridian.
  193 + """
  194 + # Initialise zenith angle map
  195 + map = gammalib.GSkyMap('CAR','CEL',0.0,0.0,-1.0,1.0,360,180)
  196 +
  197 + # Set hour angle and declination vectors
  198 + hours = [float(i) for i in range(360)]
  199 + decs = [float(i)-89.5 for i in range(180)]
  200 +
  201 + # Get array geographic longitude and latitude
  202 + geolon = self['geolon'].real()
  203 + geolat = self['geolat'].real()
  204 +
  205 + # Precompute latitude terms
  206 + cos_lat = math.cos(geolat*gammalib.deg2rad)
  207 + sin_lat = math.sin(geolat*gammalib.deg2rad)
  208 +
  209 + # Loop over all declination and hour angles and compute the zenith
  210 + # angle. Store the zenith angle in the map
  211 + index = 0
  212 + for dec in decs:
  213 + cos_dec = math.cos(dec*gammalib.deg2rad)
  214 + sin_dec = math.sin(dec*gammalib.deg2rad)
  215 + for h in hours:
  216 + cos_h = math.cos(h*gammalib.deg2rad)
  217 + zenith = math.acos(sin_lat*sin_dec +
  218 + cos_lat*cos_dec*cos_h)*gammalib.rad2deg
  219 + map[index] = zenith
  220 + index += 1
  221 +
  222 + # Return zenith angle map
  223 + return map
  224 +
  225 +
  226 + # Public methods
  227 + def run(self):
  228 + """
  229 + Run the script
  230 + """
  231 + # Switch screen logging on in debug mode
  232 + if self._logDebug():
  233 + self._log.cout(True)
  234 +
  235 + # Get parameters
  236 + self._get_parameters()
  237 +
  238 + # Compute visibility cube
  239 + self._visibility_cube()
  240 +
  241 + # Return
  242 + return
  243 +
  244 + def save(self):
  245 + """
  246 + Save the visibility cube
  247 + """
  248 + # Write header
  249 + self._log_header1(gammalib.TERSE, 'Save visibility cube')
  250 +
  251 + # Get outfile parameter
  252 + outfile = self['outfile'].filename()
  253 +
  254 + # Log file name
  255 + self._log_value(gammalib.TERSE, 'Visibility cube file', outfile.url())
  256 +
  257 + # Save the visibility cube
  258 + self._cube.save(outfile, self['clobber'].boolean())
  259 +
  260 + # Return
  261 + return
  262 +
  263 + def execute(self):
  264 + """
  265 + Execute the script
  266 + """
  267 + # Open logfile
  268 + self.logFileOpen()
  269 +
  270 + # Run the script
  271 + self.run()
  272 +
  273 + # Save the cube
  274 + self.save()
  275 +
  276 + # Return
  277 + return
  278 +
  279 +
  280 +# ======================== #
  281 +# Main routine entry point #
  282 +# ======================== #
  283 +if __name__ == '__main__':
  284 +
  285 + # Create instance of application
  286 + app = csviscube(sys.argv)
  287 +
  288 + # Execute application
  289 + app.execute()
... ...
test/test_csviscube.py 0 → 100755
  1 +#! /usr/bin/env python
  2 +# ==========================================================================
  3 +# This scripts performs unit tests for the csviscube script
  4 +#
  5 +# Copyright (C) 2016 Juergen Knoedlseder
  6 +#
  7 +# This program is free software: you can redistribute it and/or modify
  8 +# it under the terms of the GNU General Public License as published by
  9 +# the Free Software Foundation, either version 3 of the License, or
  10 +# (at your option) any later version.
  11 +#
  12 +# This program is distributed in the hope that it will be useful,
  13 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
  14 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15 +# GNU General Public License for more details.
  16 +#
  17 +# You should have received a copy of the GNU General Public License
  18 +# along with this program. If not, see <http://www.gnu.org/licenses/>.
  19 +#
  20 +# ==========================================================================
  21 +import gammalib
  22 +import cscripts
  23 +from testing import test
  24 +
  25 +
  26 +# =============================== #
  27 +# Test class for csviscube script #
  28 +# =============================== #
  29 +class Test(test):
  30 + """
  31 + Test class for csviscube script
  32 + """
  33 +
  34 + # Constructor
  35 + def __init__(self):
  36 + """
  37 + Constructor.
  38 + """
  39 + # Call base class constructor
  40 + test.__init__(self)
  41 +
  42 + # Return
  43 + return
  44 +
  45 + # Set test functions
  46 + def set(self):
  47 + """
  48 + Set all test functions.
  49 + """
  50 + # Set test name
  51 + self.name('csviscube')
  52 +
  53 + # Append tests
  54 + self.append(self._test_cmd, 'Test csviscube on command line')
  55 + self.append(self._test_python, 'Test csviscube from Python')
  56 +
  57 + # Return
  58 + return
  59 +
  60 + # Test csviscube on command line
  61 + def _test_cmd(self):
  62 + """
  63 + Test csviscube on the command line.
  64 + """
  65 + # Set script name
  66 + csviscube = self._script('csviscube')
  67 +
  68 + # Setup csviscube command
  69 + cmd = csviscube+' tmin=0.0 tmax=31557600.0 geolon=79.4041'+\
  70 + ' geolat=-24.6272 outfile=csviscube_cmd1.fits'+\
  71 + ' logfile="csviscube_cmd1.log" chatter=1'
  72 +
  73 + # Check if execution of wrong command fails
  74 + self.test_assert(self._execute('command_that_does_not_exist') != 0,
  75 + 'Self test of test script')
  76 +
  77 + # Check if execution was successful
  78 + self.test_assert(self._execute(cmd) == 0,
  79 + 'Check successful execution from command line')
  80 +
  81 + # Return
  82 + return
  83 +
  84 + # Test csviscube from Python
  85 + def _test_python(self):
  86 + """
  87 + Test csviscube from Python.
  88 + """
  89 + # Set-up csviscube
  90 + viscube = cscripts.csviscube()
  91 + viscube['tmin'] = 0.0
  92 + viscube['tmax'] = 31557600.0
  93 + viscube['geolon'] = 79.4041
  94 + viscube['geolat'] = -24.6272
  95 + viscube['outfile'] = 'csviscube_py1.fits'
  96 + viscube['logfile'] = 'csviscube_py1.log'
  97 + viscube['chatter'] = 2
  98 +
  99 + # Run script
  100 + viscube.logFileOpen() # Make sure we get a log file
  101 + viscube.run()
  102 +
  103 + # Return
  104 + return
... ...
test/test_python_cscripts.py
... ... @@ -41,6 +41,7 @@ import test_csspec
41 41 import test_cstsdist
42 42 import test_cstsmapmerge
43 43 import test_cstsmapsplit
  44 +import test_csviscube
44 45 import test_csworkflow
45 46 import test_csiactcopy
46 47 import test_csiactdata
... ... @@ -124,6 +125,7 @@ def test(installed=False, debug=False):
124 125 test_cstsdist.Test(),
125 126 test_cstsmapmerge.Test(),
126 127 test_cstsmapsplit.Test(),
  128 + test_csviscube.Test(),
127 129 test_csworkflow.Test()]
128 130  
129 131 # Append tests for Python 2.6+ (the IACT cscripts depend on the json
... ...