Commit 92a76a88ae001841e02dd0672882e083bee8562e

Authored by Luigi Tibaldo
1 parent 2a1dd3ad

parallelised cssens

cscripts/cssens.par
... ... @@ -45,6 +45,7 @@ statistic, s, h, DEFAULT,DEFAULT|CSTAT|WSTAT|CHI2,, "Optimization statistic"
45 45 #
46 46 # Standard parameters
47 47 #====================
  48 +nthreads, i, h, 0,0,1000, "Number of parallel processes (0=use all available CPUs)"
48 49 chatter, i, h, 2,0,4, "Chattiness of output"
49 50 clobber, b, h, yes,,, "Overwrite existing output files with new output files?"
50 51 debug, b, h, no,,, "Activate debugging mode?"
... ...
cscripts/cssens.py
... ... @@ -25,6 +25,33 @@ import ctools
25 25 from cscripts import obsutils
26 26 from cscripts import modutils
27 27 from cscripts import ioutils
  28 +from cscripts import mputils
  29 +
  30 +# ============================================ #
  31 +# Global functions for multiprocessing support #
  32 +# ============================================ #
  33 +def _multiprocessing_func_wrapper(args):
  34 + return _multiprocessing_func(*args)
  35 +def _multiprocessing_func(cls, i):
  36 +
  37 + # Initialise thread logger
  38 + cls._log.clear()
  39 + cls._log.buffer_size(100000)
  40 +
  41 + # Compute light curve bin
  42 + cstart = cls.celapse()
  43 + result = cls._e_bin(i)
  44 + celapse = cls.celapse() - cstart
  45 + buffer = cls._log.buffer()
  46 +
  47 + # Close logger
  48 + cls._log.close()
  49 +
  50 + # Collect thread information
  51 + info = {'celapse': celapse, 'log': buffer}
  52 +
  53 + # Return light curve bin result and thread information
  54 + return result, info
28 55  
29 56  
30 57 # ============ #
... ... @@ -57,10 +84,57 @@ class cssens(ctools.csobservation):
57 84 self._ra = None
58 85 self._dec = None
59 86 self._log_clients = False
  87 + self._models = gammalib.GModels()
  88 + self._nthreads = 0
60 89  
61 90 # Return
62 91 return
63 92  
  93 + # State methods por pickling
  94 + def __getstate__(self):
  95 + """
  96 + Extend ctools.csobservation getstate method to include some members
  97 +
  98 + Returns
  99 + -------
  100 + state : dict
  101 + Pickled instance
  102 + """
  103 + # Set pickled dictionary
  104 + state = {'base' : ctools.csobservation.__getstate__(self),
  105 + 'ebounds' : self._ebounds,
  106 + 'obs_ebounds' : self._obs_ebounds,
  107 + 'srcname' : self._srcname,
  108 + 'ra' : self._ra,
  109 + 'dec' : self._dec,
  110 + 'log_clients' : self._log_clients,
  111 + 'models' : self._models,
  112 + 'nthreads' : self._nthreads}
  113 +
  114 + # Return pickled dictionary
  115 + return state
  116 +
  117 + def __setstate__(self, state):
  118 + """
  119 + Extend ctools.csobservation setstate method to include some members
  120 +
  121 + Parameters
  122 + ----------
  123 + state : dict
  124 + Pickled instance
  125 + """
  126 + ctools.csobservation.__setstate__(self, state['base'])
  127 + self._ebounds = state['ebounds']
  128 + self._obs_ebounds = state['obs_ebounds']
  129 + self._srcname = state['srcname']
  130 + self._ra = state['ra']
  131 + self._dec = state['dec']
  132 + self._log_clients = state['log_clients']
  133 + self._models = state['models']
  134 + self._nthreads = state['nthreads']
  135 +
  136 + # Return
  137 + return
64 138  
65 139 # Private methods
66 140 def _get_parameters(self):
... ... @@ -108,6 +182,10 @@ class cssens(ctools.csobservation):
108 182 # Write input parameters into logger
109 183 self._log_parameters(gammalib.TERSE)
110 184  
  185 +
  186 + # Set number of processes for multiprocessing
  187 + self._nthreads = mputils.nthreads(self)
  188 +
111 189 # Return
112 190 return
113 191  
... ... @@ -564,6 +642,36 @@ class cssens(ctools.csobservation):
564 642 return (crab_flux_prediction, regcoeff)
565 643  
566 644  
  645 + def _e_bin(self,ieng):
  646 + """
  647 + Determines sensivity in energy bin ieng
  648 + :param ieng: int, energy bin number
  649 + :return: dict,
  650 + Result dictionary
  651 + """
  652 +
  653 + # Get sensitivity type
  654 + sensitivity_type = self['type'].string()
  655 +
  656 + # Set energies
  657 + if sensitivity_type == 'Differential':
  658 + emin = self._ebounds.emin(ieng)
  659 + emax = self._ebounds.emax(ieng)
  660 + elif sensitivity_type == 'Integral':
  661 + emin = self._ebounds.emin(ieng)
  662 + emax = self._ebounds.emax()
  663 + else:
  664 + msg = ('Invalid sensitivity type "%s" encountered. Either '
  665 + 'specify "Differential" or "Integral".' %
  666 + sensitivity_type)
  667 + raise RuntimeError(msg)
  668 +
  669 + # Determine sensitivity
  670 + result = self._get_sensitivity(emin, emax, self._models)
  671 +
  672 + # Return results
  673 + return result
  674 +
567 675 # Public methods
568 676 def run(self):
569 677 """
... ... @@ -588,48 +696,54 @@ class cssens(ctools.csobservation):
588 696 results = []
589 697  
590 698 # Set test source model for this observation
591   - models = modutils.test_source(self.obs().models(), self._srcname,
  699 + self._models = modutils.test_source(self.obs().models(), self._srcname,
592 700 ra=self._ra, dec=self._dec)
593 701  
594 702 # Write observation into logger
595 703 self._log_observations(gammalib.NORMAL, self.obs(), 'Input observation')
596 704  
597 705 # Write models into logger
598   - self._log_models(gammalib.NORMAL, models, 'Input model')
599   -
600   - # Get sensitivity type
601   - sensitivity_type = self['type'].string()
  706 + self._log_models(gammalib.NORMAL, self._models, 'Input model')
602 707  
603 708 # Write header
604 709 self._log_header1(gammalib.TERSE, 'Sensitivity determination')
605   - self._log_value(gammalib.TERSE, 'Type', sensitivity_type)
606   -
607   - # Loop over energy bins
608   - for ieng in range(self._ebounds.size()):
609   -
610   - # Set energies
611   - if sensitivity_type == 'Differential':
612   - emin = self._ebounds.emin(ieng)
613   - emax = self._ebounds.emax(ieng)
614   - elif sensitivity_type == 'Integral':
615   - emin = self._ebounds.emin(ieng)
616   - emax = self._ebounds.emax()
617   - else:
618   - msg = ('Invalid sensitivity type "%s" encountered. Either '
619   - 'specify "Differential" or "Integral".' %
620   - sensitivity_type)
621   - raise RuntimeError(msg)
  710 + self._log_value(gammalib.TERSE, 'Type', self['type'].string())
  711 +
  712 + # If using multiprocessing
  713 + if self._nthreads > 1 and self._ebounds.size() > 1:
  714 +
  715 + # Create pool of workers
  716 + from multiprocessing import Pool
  717 + pool = Pool(processes = self._nthreads)
622 718  
623   - # Determine sensitivity
624   - result = self._get_sensitivity(emin, emax, models)
  719 + # Run energy bin analysis in parallel with map
  720 + args = [(self, ieng) for ieng in range(self._ebounds.size())]
  721 + poolresults = pool.map(_multiprocessing_func_wrapper, args)
625 722  
626   - # Write out trial result
  723 + # Close pool and join
  724 + pool.close()
  725 + pool.join()
  726 +
  727 + # Construct results
  728 + for ieng in range(self._ebounds.size()):
  729 + results.append(poolresults[ieng][0])
  730 + self._log_string(gammalib.TERSE, poolresults[ieng][1]['log'], False)
  731 +
  732 + # Otherwise, loop over energy bins
  733 + else:
  734 + for ieng in range(self._ebounds.size()):
  735 +
  736 + #Run analysis in energy bin
  737 + result = self._e_bin(ieng)
  738 +
  739 + # Append results
  740 + results.append(result)
  741 +
  742 + # Write out trial result
  743 + for ieng, result in enumerate(results):
627 744 ioutils.write_csv_row(self['outfile'].filename().url(), ieng,
628 745 colnames, result)
629 746  
630   - # Append results
631   - results.append(result)
632   -
633 747 # Return
634 748 return
635 749  
... ...
test/test_cssens.py
... ... @@ -109,7 +109,7 @@ class Test(test):
109 109 """
110 110 Test cssens from Python
111 111 """
112   - # Set-up cssens for differential sensitivity computation
  112 + # Set-up cssens for differential sensitivity computation with multiprocessing
113 113 sens = cscripts.cssens()
114 114 sens['inobs'] = 'NONE'
115 115 sens['inmodel'] = self._model
... ... @@ -121,7 +121,7 @@ class Test(test):
121 121 sens['rad'] = 3.0
122 122 sens['emin'] = 1.0
123 123 sens['emax'] = 10.0
124   - sens['bins'] = 1
  124 + sens['bins'] = 2
125 125 sens['logfile'] = 'cssens_py1.log'
126 126 sens['chatter'] = 4
127 127  
... ... @@ -131,9 +131,9 @@ class Test(test):
131 131 sens.save()
132 132  
133 133 # Check pull distribution file
134   - self._check_result_file('cssens_py1.dat')
  134 + self._check_result_file('cssens_py1.dat',nrows=3)
135 135  
136   - # Set-up cssens for integral sensitivity computation
  136 + # Set-up cssens for integral sensitivity computation without multiprocessing
137 137 sens = cscripts.cssens()
138 138 sens['inobs'] = 'NONE'
139 139 sens['inmodel'] = self._model
... ... @@ -149,6 +149,7 @@ class Test(test):
149 149 sens['type'] = 'Integral'
150 150 sens['logfile'] = 'cssens_py2.log'
151 151 sens['chatter'] = 4
  152 + sens['nthreads'] = 1
152 153  
153 154 # Execute cssens script
154 155 sens.execute()
... ... @@ -160,7 +161,7 @@ class Test(test):
160 161 return
161 162  
162 163 # Check result file
163   - def _check_result_file(self, filename):
  164 + def _check_result_file(self, filename, nrows=2):
164 165 """
165 166 Check result file
166 167 """
... ... @@ -168,7 +169,7 @@ class Test(test):
168 169 results = gammalib.GCsv(filename, ',')
169 170  
170 171 # Check dimensions
171   - self.test_value(results.nrows(), 2,
  172 + self.test_value(results.nrows(), nrows,
172 173 'Check number of rows in sensitivity file')
173 174 self.test_value(results.ncols(), 10,
174 175 'Check number of columns in sensitivity file')
... ...