Commit abb29aa8bbf3336fcd96b6470f086e97f72095db

Authored by Luigi Tibaldo
1 parent 8ebb4df8

csphasecrv parallelized

cscripts/csphasecrv.par
... ... @@ -55,12 +55,13 @@ statistic, s, h, DEFAULT,DEFAULT|CSTAT|WSTAT|CHI2,, "Optimization statistic"
55 55 #
56 56 # Standard parameters
57 57 #====================
58   -publish, b, h, no,,, "Publish phase curve on VO Hub?"
59   -chatter, i, h, 2,0,4, "Chattiness of output"
60   -clobber, b, h, yes,,, "Overwrite existing output files with new output files?"
61   -debug, b, h, no,,, "Activate debugging mode?"
62   -mode, s, h, ql,,, "Mode of automatic parameters"
63   -logfile, f, h, csphasecrv.log,,, "Log filename"
  58 +nthreads, i, h, 0,0,1000, "Number of parallel processes (0=use all available CPUs)"
  59 +publish, b, h, no,,, "Publish phase curve on VO Hub?"
  60 +chatter, i, h, 2,0,4, "Chattiness of output"
  61 +clobber, b, h, yes,,, "Overwrite existing output files with new output files?"
  62 +debug, b, h, no,,, "Activate debugging mode?"
  63 +mode, s, h, ql,,, "Mode of automatic parameters"
  64 +logfile, f, h, csphasecrv.log,,, "Log filename"
64 65  
65 66  
66 67  
... ...
cscripts/csphasecrv.py
... ... @@ -23,6 +23,33 @@ import gammalib
23 23 import ctools
24 24 from cscripts import obsutils
25 25 from cscripts import ioutils
  26 +from cscripts import mputils
  27 +
  28 +# ============================================ #
  29 +# Global functions for multiprocessing support #
  30 +# ============================================ #
  31 +def _multiprocessing_func_wrapper(args):
  32 + return _multiprocessing_func(*args)
  33 +def _multiprocessing_func(cls, phbin):
  34 +
  35 + # Initialise thread logger
  36 + cls._log.clear()
  37 + cls._log.buffer_size(100000)
  38 +
  39 + # Compute light curve bin
  40 + cstart = cls.celapse()
  41 + result = cls._phase_bin(phbin)
  42 + celapse = cls.celapse() - cstart
  43 + buffer = cls._log.buffer()
  44 +
  45 + # Close logger
  46 + cls._log.close()
  47 +
  48 + # Collect thread information
  49 + info = {'celapse': celapse, 'log': buffer}
  50 +
  51 + # Return light curve bin result and thread information
  52 + return result, info
26 53  
27 54  
28 55 # ================ #
... ... @@ -71,6 +98,50 @@ class csphasecrv(ctools.csobservation):
71 98 # Return
72 99 return
73 100  
  101 + # State methods por pickling
  102 + def __getstate__(self):
  103 + """
  104 + Extend ctools.csobservation getstate method to include some members
  105 +
  106 + Returns
  107 + -------
  108 + state : dict
  109 + Pickled instance
  110 + """
  111 + # Set pickled dictionary
  112 + state = {'base' : ctools.csobservation.__getstate__(self),
  113 + 'srcname' : self._srcname,
  114 + 'phbins' : self._phbins,
  115 + 'stacked' : self._stacked,
  116 + 'onoff' : self._onoff,
  117 + 'fits' : self._fits,
  118 + 'fitmodels': self._fitmodels,
  119 + 'nthreads' : self._nthreads}
  120 +
  121 + # Return pickled dictionary
  122 + return state
  123 +
  124 + def __setstate__(self, state):
  125 + """
  126 + Extend ctools.csobservation setstate method to include some members
  127 +
  128 + Parameters
  129 + ----------
  130 + state : dict
  131 + Pickled instance
  132 + """
  133 + ctools.csobservation.__setstate__(self, state['base'])
  134 + self._srcname = state['srcname']
  135 + self._phbins = state['phbins']
  136 + self._onoff = state['onoff']
  137 + self._stacked = state['stacked']
  138 + self._fits = state['fits']
  139 + self._fitmodels= state['fitmodels']
  140 + self._nthreads = state['nthreads']
  141 +
  142 + # Return
  143 + return
  144 +
74 145 # Private methods
75 146 def _get_parameters(self):
76 147 """
... ... @@ -111,6 +182,9 @@ class csphasecrv(ctools.csobservation):
111 182 # Write input parameters into logger
112 183 self._log_parameters(gammalib.TERSE)
113 184  
  185 + # Set number of processes for multiprocessing
  186 + self._nthreads = mputils.nthreads(self)
  187 +
114 188 # Return
115 189 return
116 190  
... ... @@ -320,6 +394,79 @@ class csphasecrv(ctools.csobservation):
320 394 # Return
321 395 return
322 396  
  397 + def _phase_bin(self,phbin):
  398 +
  399 + # Write time bin into header
  400 + self._log_header2(gammalib.TERSE, 'PHASE %f - %f' %
  401 + (phbin[0], phbin[1]))
  402 +
  403 + # Select events
  404 + select = ctools.ctselect(self.obs().copy())
  405 + select['emin'] = self['emin'].real()
  406 + select['emax'] = self['emax'].real()
  407 + select['tmin'] = 'UNDEFINED'
  408 + select['tmax'] = 'UNDEFINED'
  409 + select['rad'] = 'UNDEFINED'
  410 + select['ra'] = 'UNDEFINED'
  411 + select['dec'] = 'UNDEFINED'
  412 + select['expr'] = 'PHASE>' + str(phbin[0]) + ' && PHASE<' + str(phbin[1])
  413 + select.run()
  414 +
  415 + # Set phase string
  416 + phstr = str(phbin[0]) + '-' + str(phbin[1])
  417 +
  418 + # Add phase to observation id
  419 + for i in range(0, select.obs().size()):
  420 + oldid = select.obs()[i].id()
  421 + select.obs()[i].id(oldid + '_' + phstr)
  422 + obs = select.obs()
  423 +
  424 + # If an On/Off analysis is requested generate the On/Off observations
  425 + if self._onoff:
  426 + obs = obsutils.get_onoff_obs(self, select.obs())
  427 +
  428 + # ... otherwise, if stacked analysis is requested then bin the
  429 + # events and compute the stacked response functions and setup
  430 + # an observation container with a single stacked observation.
  431 + elif self._stacked:
  432 + obs = obsutils.get_stacked_obs(self, select.obs())
  433 +
  434 + # Header
  435 + self._log_header3(gammalib.EXPLICIT, 'Fitting the data')
  436 +
  437 + # The On/Off analysis can produce empty observation containers,
  438 + # e.g., when on-axis observations are used. To avoid ctlike asking
  439 + # for a new observation container (or hang, if in interactive mode)
  440 + # we'll run ctlike only if the size is >0
  441 + if obs.size() > 0:
  442 +
  443 + # Do maximum likelihood model fitting
  444 + like = ctools.ctlike(obs)
  445 + like['edisp'] = self['edisp'].boolean()
  446 + like.run()
  447 +
  448 + # Renormalize models to phase selection
  449 + # TODO move the scaling from the temporal to the spectral component
  450 + for model in like.obs().models():
  451 + scaled_norm = model['Normalization'].value() / (phbin[1] - phbin[0])
  452 + model['Normalization'].value(scaled_norm)
  453 +
  454 + # Store fit model
  455 + fitmodels = like.obs().models().copy()
  456 +
  457 + # ... otherwise we set an empty model container
  458 + else:
  459 + self._log_string(gammalib.TERSE,
  460 + 'PHASE %f - %f: no observations available'
  461 + ' for fitting' % (phbin[0], phbin[1]))
  462 +
  463 + # Set empty models container
  464 + fitmodels = gammalib.GModels()
  465 +
  466 + result = {'phstr' :phstr,
  467 + 'fitmodels' :fitmodels}
  468 +
  469 + return result
323 470  
324 471 # Public methods
325 472 def run(self):
... ... @@ -336,84 +483,37 @@ class csphasecrv(ctools.csobservation):
336 483 # Write observation into logger
337 484 self._log_observations(gammalib.NORMAL, self.obs(), 'Observation')
338 485  
339   - # Store and clear observations to split them into phase bins
340   - orig_obs = self.obs().copy()
341   -
342 486 # Dictionary to save phase fitted models
343 487 self._fitmodels = {}
344 488  
345 489 # Write header
346 490 self._log_header1(gammalib.TERSE, 'Generate phase curve')
347 491  
348   - # Loop over all phases
349   - for phbin in self._phbins:
  492 + # If using multiprocessing
  493 + if self._nthreads > 1:
350 494  
351   - # Write time bin into header
352   - self._log_header2(gammalib.TERSE, 'PHASE %f - %f' %
353   - (phbin[0], phbin[1]))
354   -
355   - # Select events
356   - select = ctools.ctselect(orig_obs)
357   - select['emin'] = self['emin'].real()
358   - select['emax'] = self['emax'].real()
359   - select['tmin'] = 'UNDEFINED'
360   - select['tmax'] = 'UNDEFINED'
361   - select['rad'] = 'UNDEFINED'
362   - select['ra'] = 'UNDEFINED'
363   - select['dec'] = 'UNDEFINED'
364   - select['expr'] = 'PHASE>'+str(phbin[0])+' && PHASE<'+str(phbin[1])
365   - select.run()
366   -
367   - # Set phase string
368   - phstr = str(phbin[0]) + '-' + str(phbin[1])
369   -
370   - # Add phase to observation id
371   - for i in range(0,select.obs().size()):
372   - oldid = select.obs()[i].id()
373   - select.obs()[i].id(oldid+'_'+phstr)
374   - obs = select.obs()
375   -
376   - # If an On/Off analysis is requested generate the On/Off observations
377   - if self._onoff:
378   - obs = obsutils.get_onoff_obs(self, select.obs())
379   -
380   - # ... otherwise, if stacked analysis is requested then bin the
381   - # events and compute the stacked response functions and setup
382   - # an observation container with a single stacked observation.
383   - elif self._stacked:
384   - obs = obsutils.get_stacked_obs(self, select.obs())
385   -
386   - # Header
387   - self._log_header3(gammalib.EXPLICIT, 'Fitting the data')
388   -
389   - # The On/Off analysis can produce empty observation containers,
390   - # e.g., when on-axis observations are used. To avoid ctlike asking
391   - # for a new observation container (or hang, if in interactive mode)
392   - # we'll run ctlike only if the size is >0
393   - if obs.size() > 0:
394   -
395   - # Do maximum likelihood model fitting
396   - like = ctools.ctlike(obs)
397   - like['edisp'] = self['edisp'].boolean()
398   - like.run()
399   -
400   - # Renormalize models to phase selection
401   - # TODO move the scaling from the temporal to the spectral component
402   - for model in like.obs().models():
403   - scaled_norm = model['Normalization'].value()/(phbin[1]-phbin[0])
404   - model['Normalization'].value(scaled_norm)
405   -
406   - # Store fit model
407   - self._fitmodels[phstr] = like.obs().models().copy()
408   -
409   - # ... otherwise we set an empty model container
410   - else:
411   - self._log_string(gammalib.TERSE,
412   - 'PHASE %f - %f: no observations available'
413   - ' for fitting' %(phbin[0], phbin[1]))
  495 + # Create pool of workers
  496 + from multiprocessing import Pool
  497 + pool = Pool(processes = self._nthreads)
  498 +
  499 + # Run time bin analysis in parallel with map
  500 + args = [(self, phbin) for phbin in self._phbins]
  501 + poolresults = pool.map(_multiprocessing_func_wrapper, args)
  502 +
  503 + # Close pool and join
  504 + pool.close()
  505 + pool.join()
  506 +
  507 + # Construct results
  508 + for i in range(len(self._phbins)):
  509 + self._fitmodels[poolresults[i][0]['phstr']] = poolresults[i][0]['fitmodels']
  510 + self._log_string(gammalib.TERSE, poolresults[i][1]['log'], False)
414 511  
415   - # Set empty models container
416   - self._fitmodels[phstr] = gammalib.GModels()
  512 + # Otherwise, loop over all phases
  513 + else:
  514 + for phbin in self._phbins:
  515 + result = self._phase_bin(phbin)
  516 + self._fitmodels[result['phstr']] = result['fitmodels']
417 517  
418 518 # Create FITS file
419 519 self._create_fits()
... ...