Commit 92a76a88ae001841e02dd0672882e083bee8562e
1 parent
2a1dd3ad
parallelised cssens
Showing
3 changed files
with
150 additions
and
34 deletions
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') | ... | ... |