ctools  2.0.0
 All Classes Namespaces Files Functions Variables Macros Pages
csviscube.py
Go to the documentation of this file.
1 #! /usr/bin/env python
2 # ==========================================================================
3 # Compute a visibility cube
4 #
5 # Copyright (C) 2016-2021 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 math
23 import gammalib
24 import ctools
25 
26 
27 # =============== #
28 # csviscube class #
29 # =============== #
30 class csviscube(ctools.cscript):
31  """
32  Compute a visibility cube
33  """
34 
35  # Constructor
36  def __init__(self, *argv):
37  """
38  Constructor
39 
40  Parameters
41  ----------
42  argv : list of str
43  List of IRAF command line parameter strings of the form
44  ``parameter=3``.
45 
46  Raises
47  ------
48  TypeError
49  An invalid number of command line arguments was provided.
50  """
51  # Initialise application by calling the base class constructor
52  self._init_cscript(self.__class__.__name__, ctools.__version__, argv)
53 
54  # Initialise members
55  self._cube = gammalib.GSkyMap()
56  self._results = []
57 
58  # Return
59  return
60 
61 
62  # Private methods
63  def _get_parameters(self):
64  """
65  Get all parameters
66  """
67  # Query parameters
68  self['mjdref'].real()
69  self['tmin'].time()
70  self['tmax'].time()
71  self['geolon'].real()
72  self['geolat'].real()
73  self['outfile'].filename()
74 
75  # Write input parameters into logger
76  self._log_parameters(gammalib.TERSE)
77 
78  # Return
79  return
80 
81  def _zenith_angle_map(self, nx, ny, dx, dy):
82  """
83  Compute zenith angle map
84 
85  The zenith angle of a position (ra,dec) depends on the declination and
86  the hour angle h and is given by
87 
88  zenith(h,dec) = arccos( sin(lat) * sin(dec) + cos(lat) * cos(dec) * cos(h) )
89 
90  The hour angle h (or local hour angle, LHA) is defined as the difference
91  between local siderial time (LST) and the Right Ascension
92 
93  h = LST - ra
94 
95  The map is computed for h=-ra which is equivalent to GST=lon (or LST=0).
96  In other words, the map corresponds to the time when ra=0 passes through
97  the local meridian.
98 
99  Parameters
100  ----------
101  nx : int
102  Number of Right Ascension pixels.
103  ny : int
104  Number of Declination pixels.
105  dx : float
106  Right Ascension pixel size in degrees.
107  dy : float
108  Declination pixel size in degrees.
109 
110  Returns
111  -------
112  zmap : `~gammalib.GSkyMap`
113  Allsky map comprising the zenith angle for an hour angle of 0.
114  """
115  # Initialise zenith angle map
116  zmap = gammalib.GSkyMap('CAR','CEL',0.0,0.0,-dx,dy,nx,ny)
117 
118  # Set hour angle and declination vectors
119  hours = [(float(i)+0.5)*dx for i in range(nx)]
120  decs = [(float(i)+0.5)*dy-90.0 for i in range(ny)]
121 
122  # Get array geographic latitude
123  geolat = self['geolat'].real()
124 
125  # Precompute latitude terms
126  cos_lat = math.cos(geolat*gammalib.deg2rad)
127  sin_lat = math.sin(geolat*gammalib.deg2rad)
128 
129  # Loop over all declinations and hour angles and compute the zenith
130  # angle. Store the zenith angle in the map
131  index = 0
132  for dec in decs:
133  cos_dec = math.cos(dec*gammalib.deg2rad)
134  sin_dec = math.sin(dec*gammalib.deg2rad)
135  for h in hours:
136  cos_h = math.cos(h*gammalib.deg2rad)
137  zenith = math.acos(sin_lat*sin_dec +
138  cos_lat*cos_dec*cos_h)*gammalib.rad2deg
139  zmap[index] = zenith
140  index += 1
141 
142  # Log zenith angle map
143  self._log_header2(gammalib.EXPLICIT, 'Zenith angle map')
144  self._log_string(gammalib.EXPLICIT, str(zmap))
145 
146  # Return zenith angle map
147  return zmap
148 
149  def _sun_ra_exclusion(self, time):
150  """
151  Compute the half length of the Right Ascension interval to exclude due
152  to the Sun constraint
153 
154  The Sun zenith angle constraint implies an interval in Right Ascension
155  that is to be excluded (that's the interval during which it is day).
156  This method computes half the length of this interval in degrees.
157 
158  Parameters
159  ----------
160  time : `~gammalib.GTime()`
161  Time for which the Sun exclusion is to be computed
162 
163  Returns
164  -------
165  dra : float
166  Right Ascension interval half length (degrees)
167  """
168  # Get array geographic latitude and minimum Sun zenith angle in radians
169  geolat = self['geolat'].real() * gammalib.deg2rad
170  sunzenith = self['sunzenith'].real() * gammalib.deg2rad
171 
172  # Compute Right Ascension and Declination of Sun in degrees and
173  # convert the Declination into radians
174  sun = gammalib.GSkyDir()
175  sun.sun(time)
176  sundec = sun.dec()
177 
178  # Compute some sines and cosines
179  cos_sunzenith = math.cos(sunzenith)
180  sin_geolat = math.sin(geolat)
181  cos_geolat = math.cos(geolat)
182  sin_sundec = math.sin(sundec)
183  cos_sundec = math.cos(sundec)
184 
185  # Compute Right Ascension difference when Sun is below the mimimum
186  # zenith angle in degrees
187  dra = 0.0
188  nom = cos_sunzenith - sin_geolat * sin_sundec
189  denom = cos_geolat * cos_sundec
190  if denom != 0.0:
191  arg = nom/denom
192  if arg >= -1.0 and arg <= 1.0:
193  dra = math.acos(arg) * gammalib.rad2deg
194 
195  # Return
196  return dra
197 
198  def _moon_ra_exclusion(self, time):
199  """
200  Compute the half length of the Right Ascension interval to exclude due
201  to the Moon constraint
202 
203  The Moon zenith angle constraint implies an interval in Right Ascension
204  that is to be excluded. This method computes half the length of this
205  interval.
206 
207  Parameters
208  ----------
209  time : `~gammalib.GTime()`
210  Time for which the Moon exclusion is to be computed
211 
212  Returns
213  -------
214  dra : float
215  Right Ascension interval half length (degrees)
216  """
217  # Get array geographic latitude and minimum Moon zenith angle in radians
218  geolat = self['geolat'].real() * gammalib.deg2rad
219  moonzenith = self['moonzenith'].real() * gammalib.deg2rad
220 
221  # Compute Right Ascension and Declination of Moon in degrees and
222  # convert the Declination into radians
223  moon = gammalib.GSkyDir()
224  moon.moon(time)
225  moondec = moon.dec()
226 
227  # Compute some sines and cosines
228  cos_moonzenith = math.cos(moonzenith)
229  sin_geolat = math.sin(geolat)
230  cos_geolat = math.cos(geolat)
231  sin_moondec = math.sin(moondec)
232  cos_moondec = math.cos(moondec)
233 
234  # Compute Right Ascension difference when Moon is below the mimimum
235  # zenith angle in degrees
236  dra = 0.0
237  nom = cos_moonzenith - sin_geolat * sin_moondec
238  denom = cos_geolat * cos_moondec
239  if denom != 0.0:
240  arg = nom/denom
241  if arg >= -1.0 and arg <= 1.0:
242  dra = math.acos(arg) * gammalib.rad2deg
243 
244  # Return
245  return dra
246 
247  def _adjust_ra_interval(self, ra_start, ra_stop):
248  """
249  Adjust Right Ascension interval so that it overlaps with the [0,360[
250  interval
251 
252  Parameters
253  ----------
254  ra_start : float
255  Start of interval to exclude (degrees)
256  ra_stop : float
257  Stop of interval to exclude (degrees)
258 
259  Returns
260  -------
261  ra_start : float
262  Adjusted start of interval to exclude (degrees)
263  ra_stop : float
264  Adjusted stop of interval to exclude (degrees)
265  """
266  # Adjust interval if both boundaries are negative
267  while ra_start < 0.0 and ra_stop < 0.0:
268  ra_start += 360.0
269  ra_stop += 360.0
270 
271  # Adjust interval if both boundaries are equal or larger than 360
272  # degrees
273  while ra_start >= 360.0 and ra_stop >= 360.0:
274  ra_start -= 360.0
275  ra_stop -= 360.0
276 
277  # Return interval
278  return ra_start, ra_stop
279 
280  def _exclude_ra_interval(self, hours, ra_start, ra_stop):
281  """
282  Exclude Right Ascension interval from array of hour angles
283 
284  Parameters
285  ----------
286  hours : list of floats
287  Array of hours
288  ra_start : float
289  Start of interval to exclude (degrees)
290  ra_stop : float
291  Stop of interval to exclude (degrees)
292 
293  Returns
294  -------
295  hours : list of floats
296  Array of hours with excluded Right Ascension interval
297  """
298  # Conversion from RA (degrees) to index
299  nhours = len(hours)
300  ra2inx = float(nhours)/360.0
301 
302  # Adjust interval
303  ra_start, ra_stop = self._adjust_ra_interval(ra_start, ra_stop)
304 
305  # Case 1: The RA interval is fully comprised within the [0,360]
306  # interval, hence we simply exclude this interval.
307  if ra_start >= 0.0 and ra_stop <= 360.0:
308  inx_start = int(ra_start * ra2inx + 0.5)
309  inx_stop = int(ra_stop * ra2inx + 0.5)
310  for i in range(inx_start,inx_stop):
311  hours[i] = 0.0
312 
313  # Case 2: The RA interval is starting at negative RA. In that case
314  # there are two intervals to exclude:
315  # [ra_start+360,360] and [0,ra_stop]
316  elif ra_start < 0.0:
317  inx_start = int((ra_start+360.0) * ra2inx + 0.5)
318  inx_stop = int(ra_stop * ra2inx + 0.5)
319  for i in range(inx_start,nhours):
320  hours[i] = 0.0
321  for i in range(0,inx_stop):
322  hours[i] = 0.0
323 
324  # Case 3: The RA interval is stopping at RA>360. In that case there
325  # are two intervals to exclude:
326  # [0,ra_stop-360] and [0,ra_start]
327  elif ra_stop > 360.0:
328  inx_start = int(ra_start * ra2inx + 0.5)
329  inx_stop = int((ra_stop-360.0) * ra2inx + 0.5)
330  for i in range(0,inx_stop):
331  hours[i] = 0.0
332  for i in range(inx_start,nhours):
333  hours[i] = 0.0
334 
335  # Return hours
336  return hours
337 
339  """
340  Compute hour angle weights
341 
342  Computes an array that specifies the time during which a given hour
343  angle was observed during the observing time interval [tmin,tmax].
344  The hour angle runs from 0 to 360 degrees, array values are in units
345  of hours.
346 
347  The method loops over the days in the time interval [tmin,tmax], with
348  time = tmin + i*86400 seconds. At each time step the position in Right
349  Ascension and Declination of the Sun is computed (the Sun's
350  Declination is in fact not used here).
351 
352  An interval of [ra_sun-dra_sun, ra_sun+dra_sun] is assumed to be the
353  day. The method _sun_ra_exclusion() is used to compute dra_sun, which
354  is half of the length of the day in degrees (recall that 15 degrees
355  is one hour). The length of the day is computed using the sunzenith
356  constraint.
357  """
358  # Write header
359  self._log_header2(gammalib.NORMAL, 'Hour angle weights')
360 
361  # Get MET time reference
362  tref = gammalib.GTimeReference(self['mjdref'].real(),'s','TT','LOCAL')
363 
364  # Get time interval
365  tmin = self['tmin'].time(tref)
366  tmax = self['tmax'].time(tref)
367 
368  # Initialise hour angle list and results
369  hour_angles = []
370  self._results = []
371 
372  # Set number of hour angle bins and compute conversion factor and weight
373  nsteps = 1000
374  ra2inx = float(nsteps)/360.0 # Conversion from RA (deg) to index
375  weight = 24.0/float(nsteps) # Weight per hour angle step
376 
377  # Initialise hour angle array and weight
378  hours = [0.0 for i in range(nsteps)]
379 
380  # Initialise time and loop until the end time is reached
381  time = tmin
382  while time <= tmax:
383 
384  # Initialise hours array for this time step
385  hours_time = [weight for i in range(nsteps)]
386 
387  # Compute half the length of the exclusion intervals in degrees
388  sun_dra = self._sun_ra_exclusion(time)
389  moon_dra = self._moon_ra_exclusion(time)
390 
391  # Compute Right Ascension and Declination of Sun in degrees and
392  # derive Sun exclusion interval
393  sun = gammalib.GSkyDir()
394  sun.sun(time)
395  sun_ra = sun.ra_deg()
396  sun_dec = sun.dec_deg()
397  sun_ra_start = sun_ra - sun_dra
398  sun_ra_stop = sun_ra + sun_dra
399 
400  # Adjust interval
401  sun_ra_start, sun_ra_stop = self._adjust_ra_interval(sun_ra_start,
402  sun_ra_stop)
403 
404  # Compute Right Ascension and Declination of Moon in degrees and
405  # derive Moon exclusion interval
406  moon = gammalib.GSkyDir()
407  moon.moon(time)
408  moon_ra = moon.ra_deg()
409  moon_dec = moon.dec_deg()
410  moon_ra_start = moon_ra - moon_dra
411  moon_ra_stop = moon_ra + moon_dra
412 
413  # Adjust interval
414  moon_ra_start, moon_ra_stop = self._adjust_ra_interval(moon_ra_start,
415  moon_ra_stop)
416 
417  # Compute Moon elongation and illumation fraction (Moon phase)
418  elongation = sun.dist_deg(moon)
419  fli = (1.0 - math.cos(elongation * gammalib.deg2rad))/2.0
420 
421  # Exclude hours due to Sun constraint (this defines the night)
422  hours_time = self._exclude_ra_interval(hours_time, sun_ra_start,
423  sun_ra_stop)
424 
425  # Exclude hours due to Moon constraint if the illumination fraction
426  # is equal to or above the maximim fraction of illumination
427  if fli >= self['maxfli'].real():
428  hours_time = self._exclude_ra_interval(hours_time, moon_ra_start,
429  moon_ra_stop)
430 
431  # Add hours
432  dark_time = 0.0
433  for i in range(nsteps):
434  hours[i] += hours_time[i]
435  dark_time += hours_time[i]
436 
437  # Set result record
438  result = {'time': time.copy(),
439  'sun_ra': sun_ra, 'sun_dec': sun_dec,
440  'moon_ra': moon_ra, 'moon_dec': moon_dec,
441  'elongation': elongation, 'fli': fli,
442  'sun_ra_start': sun_ra_start, 'sun_ra_stop': sun_ra_stop,
443  'moon_ra_start': moon_ra_start, 'moon_ra_stop': moon_ra_stop,
444  'dark_time' : dark_time}
445 
446  # Append result record to results
447  self._results.append(result)
448 
449  # Log results
450  logs = 'Sun=(%8.3f,%7.3f) Moon=(%8.3f,%7.3f) '\
451  'Sun_RA_excl=[%8.3f,%8.3f] Moon_RA_excl=[%8.3f,%8.3f] '\
452  'FLI=%4.2f Dark time=%5.2f h' % \
453  (sun_ra, sun_dec, moon_ra, moon_dec,
454  sun_ra_start, sun_ra_stop, moon_ra_start, moon_ra_stop,
455  fli, dark_time)
456  self._log_value(gammalib.VERBOSE, time.utc(), logs)
457 
458  # Add seconds of one day and start with next day
459  time += 86400.0
460 
461  # Build hour angle list
462  dh = 360.0/float(nsteps)
463  for i in range(nsteps):
464  hour_angle = {'angle': dh*float(i), 'hours': hours[i]}
465  hour_angles.append(hour_angle)
466 
467  # Log hour angle weights
468  total = 0.0
469  for hour_angle in hour_angles:
470  total += hour_angle['hours']
471  self._log_value(gammalib.EXPLICIT, 'Observing time', str(total)+' h')
472  self._log_value(gammalib.EXPLICIT, 'Number of hour angle bins', len(hour_angles))
473 
474  # Return hour angle weights
475  return hour_angles
476 
477  def _visibility_cube(self):
478  """
479  Compute visibility cube
480 
481  Compute visibility cube by displacing the zenith angle map for all
482  hour angles. The visibility cube contains the number of hours a given
483  celestial position is visible under a given zenith angle interval.
484  Summing over all zenith angle intervals specifies for how long a given
485  celestial position will be visible.
486  """
487  # Write header
488  self._log_header1(gammalib.TERSE, 'Compute visibility cube')
489 
490  # Set visibility cube and zenith angle map dimensions and bin size
491  binsz = self['binsz'].real()
492  nx = int(360.0/binsz+0.5)
493  ny = int(180.0/binsz+0.5)
494  dx = 360.0/float(nx)
495  dy = 180.0/float(ny)
496 
497  # Set zenith angle dimension and bin size
498  dz = self['dz'].real()
499  zmax = self['zmax'].real()
500  nz = int(zmax/dz+0.5)
501  dz = zmax/float(nz)
502 
503  # Log dimensions
504  self._log_header2(gammalib.NORMAL, 'Visibility cube dimensions')
505  self._log_value(gammalib.NORMAL, 'Number of RA bins', nx)
506  self._log_value(gammalib.NORMAL, 'Number of Dec bins', ny)
507  self._log_value(gammalib.NORMAL, 'Number of zenith angles', nz)
508  self._log_value(gammalib.NORMAL, 'RA pixel size', str(dx)+' deg')
509  self._log_value(gammalib.NORMAL, 'Dec pixel size', str(dy)+' deg')
510  self._log_value(gammalib.NORMAL, 'Zenith angle bin size', str(dz)+' deg')
511  self._log_value(gammalib.NORMAL, 'Maximum zenith angle', str(zmax)+' deg')
512 
513  # Compute zenith angle map
514  zmap = self._zenith_angle_map(nx,ny,dx,dy)
515 
516  # Setup hour angle weights. They specify for how many hours a given
517  # hour angle is observed during the covered time period.
518  hour_angles = self._hour_angle_weight()
519 
520  # Initialise visibility cube
521  self._cube = gammalib.GSkyMap('CAR','CEL',0.0,0.0,-dx,dy,nx,ny,nz)
522 
523  # Loop over all hour angle weights
524  for hour_angle in hour_angles:
525 
526  # Compute temporal shift in degrees
527  shift = hour_angle['angle']
528 
529  # Initialise shifted zenith angle map
530  zmap_shift = gammalib.GSkyMap('CAR','CEL',shift,0.0,-dx,dy,nx,ny)
531 
532  # Merge zenith angle map into shifted map
533  zmap_shift += zmap
534 
535  # Loop over all pixels of the shifted map and add the hours during
536  # which the shifted map occurs to the relevant zenith angle bin
537  for i, zenith in enumerate(zmap_shift):
538  iz = int(zenith/dz)
539  if iz < nz:
540  self._cube[i,iz] += hour_angle['hours']
541 
542  # Return
543  return
544 
545  def _save_results(self, outfile, clobber):
546  """
547  Save results in VISIBILITY FITS table
548 
549  Parameters
550  ----------
551  outfile : str
552  Result FITS file name
553  clobber : bool
554  Overwrite existing file?
555  """
556  # Create FITS table columns
557  nrows = len(self._results)
558  time = gammalib.GFitsTableStringCol('Time', nrows, 20)
559  mjd = gammalib.GFitsTableDoubleCol('MJD', nrows)
560  sun_ra = gammalib.GFitsTableDoubleCol('RA_sun', nrows)
561  sun_dec = gammalib.GFitsTableDoubleCol('DEC_sun', nrows)
562  moon_ra = gammalib.GFitsTableDoubleCol('RA_moon', nrows)
563  moon_dec = gammalib.GFitsTableDoubleCol('DEC_moon', nrows)
564  sun_ra_start = gammalib.GFitsTableDoubleCol('RA_sun_start', nrows)
565  sun_ra_stop = gammalib.GFitsTableDoubleCol('RA_sun_stop', nrows)
566  moon_ra_start = gammalib.GFitsTableDoubleCol('RA_moon_start', nrows)
567  moon_ra_stop = gammalib.GFitsTableDoubleCol('RA_moon_stop', nrows)
568  elongation = gammalib.GFitsTableDoubleCol('Elongation', nrows)
569  fli = gammalib.GFitsTableDoubleCol('FLI', nrows)
570  dark_time = gammalib.GFitsTableDoubleCol('Darktime', nrows)
571 
572  # Set units of table columns
573  mjd.unit('days')
574  sun_ra.unit('deg')
575  sun_dec.unit('deg')
576  moon_ra.unit('deg')
577  moon_dec.unit('deg')
578  sun_ra_start.unit('deg')
579  sun_ra_stop.unit('deg')
580  moon_ra_start.unit('deg')
581  moon_ra_stop.unit('deg')
582  elongation.unit('deg')
583  dark_time.unit('hours')
584 
585  # File FITS table columns
586  for i, result in enumerate(self._results):
587  time[i] = result['time'].utc()
588  mjd[i] = result['time'].mjd()
589  sun_ra[i] = result['sun_ra']
590  sun_dec[i] = result['sun_dec']
591  moon_ra[i] = result['moon_ra']
592  moon_dec[i] = result['moon_dec']
593  sun_ra_start[i] = result['sun_ra_start']
594  sun_ra_stop[i] = result['sun_ra_stop']
595  moon_ra_start[i] = result['moon_ra_start']
596  moon_ra_stop[i] = result['moon_ra_stop']
597  elongation[i] = result['elongation']
598  fli[i] = result['fli']
599  dark_time[i] = result['dark_time']
600 
601  # Initialise FITS Table with extension "VISIBILITY"
602  table = gammalib.GFitsBinTable(nrows)
603  table.extname('VISIBILITY')
604 
605  # Add Header for compatibility with gammalib.GMWLSpectrum
606  table.card('INSTRUME', 'CTA', 'Name of Instrument')
607  table.card('TELESCOP', 'CTA', 'Name of Telescope')
608 
609  # Append filled columns to fits table
610  table.append(time)
611  table.append(mjd)
612  table.append(sun_ra)
613  table.append(sun_dec)
614  table.append(moon_ra)
615  table.append(moon_dec)
616  table.append(sun_ra_start)
617  table.append(sun_ra_stop)
618  table.append(moon_ra_start)
619  table.append(moon_ra_stop)
620  table.append(elongation)
621  table.append(fli)
622  table.append(dark_time)
623 
624  # Append table to result FITS file
625  fits = gammalib.GFits(outfile)
626  fits.append(table)
627  fits.save(clobber)
628 
629  # Return
630  return
631 
632 
633  # Public methods
634  def process(self):
635  """
636  Process the script
637  """
638  # Get parameters
639  self._get_parameters()
640 
641  # Compute visibility cube
642  self._visibility_cube()
643 
644  # Optionally publish map
645  if self['publish'].boolean():
646  self.publish()
647 
648  # Return
649  return
650 
651  def save(self):
652  """
653  Save the visibility cube
654  """
655  # Write header
656  self._log_header1(gammalib.TERSE, 'Save visibility cube')
657 
658  # Get outfile parameter
659  outfile = self['outfile'].filename()
660 
661  # Log file name
662  self._log_value(gammalib.NORMAL, 'Visibility cube file', outfile.url())
663 
664  # Save the visibility cube
665  self._cube.save(outfile, self['clobber'].boolean())
666 
667  # Save the results in VISIBILITY table extension
668  self._save_results(outfile, self['clobber'].boolean())
669 
670  # Stamp visibility cube
671  self._stamp(outfile)
672 
673  # Return
674  return
675 
676  def publish(self, name=''):
677  """
678  Publish visibility cube
679 
680  Parameters
681  ----------
682  name : str, optional
683  Name of visibility cube
684  """
685  # Write header
686  self._log_header1(gammalib.TERSE, 'Publish visibility cube')
687 
688  # Set default name is user name is empty
689  if not name:
690  user_name = self._name()
691  else:
692  user_name = name
693 
694  # Log cube name
695  self._log_value(gammalib.TERSE, 'Visibility cube name', user_name)
696 
697  # Publish cube
698  self._cube.publish(user_name)
699 
700  # Return
701  return
702 
703 
704 # ======================== #
705 # Main routine entry point #
706 # ======================== #
707 if __name__ == '__main__':
708 
709  # Create instance of application
710  app = csviscube(sys.argv)
711 
712  # Execute application
713  app.execute()