Commit 9b24f41f0ffc461c5a5136969d6f08a6af38b3ba

Authored by Jürgen Knödlseder
1 parent d3d48015

Add mputils.process() function and reduce code duplication

cscripts/cslightcrv.py
@@ -647,20 +647,13 @@ class cslightcrv(ctools.csobservation): @@ -647,20 +647,13 @@ class cslightcrv(ctools.csobservation):
647 # Write header 647 # Write header
648 self._log_header1(gammalib.TERSE, 'Generate lightcurve') 648 self._log_header1(gammalib.TERSE, 'Generate lightcurve')
649 649
650 - # If using multiprocessing 650 + # If more than a single thread is requested then use multiprocessing
651 if self._nthreads > 1: 651 if self._nthreads > 1:
652 652
653 - # Create pool of workers  
654 - from multiprocessing import Pool  
655 - pool = Pool(processes = self._nthreads)  
656 -  
657 - # Run time bin analysis in parallel with map 653 + # Compute time bins
658 args = [(self, i) for i in range(self._tbins.size())] 654 args = [(self, i) for i in range(self._tbins.size())]
659 - poolresults = pool.map(_multiprocessing_func_wrapper, args)  
660 -  
661 - # Close pool and join  
662 - pool.close()  
663 - pool.join() 655 + poolresults = mputils.process(self._nthreads,
  656 + _multiprocessing_func_wrapper, args)
664 657
665 # Construct results 658 # Construct results
666 results = [] 659 results = []
cscripts/csphagen.py
@@ -578,17 +578,10 @@ class csphagen(ctools.csobservation): @@ -578,17 +578,10 @@ class csphagen(ctools.csobservation):
578 # If there is more than one observation and we use multiprocessing 578 # If there is more than one observation and we use multiprocessing
579 if self._nthreads > 1 and self.obs().size() > 1: 579 if self._nthreads > 1 and self.obs().size() > 1:
580 580
581 - # Create pool of workers  
582 - from multiprocessing import Pool  
583 - pool = Pool(processes = self._nthreads)  
584 -  
585 - # Run time bin analysis in parallel with map 581 + # Compute observations
586 args = [(self, i) for i in range(self.obs().size())] 582 args = [(self, i) for i in range(self.obs().size())]
587 - poolresults = pool.map(_multiprocessing_func_wrapper, args)  
588 -  
589 - # Close pool and join  
590 - pool.close()  
591 - pool.join() 583 + poolresults = mputils.process(self._nthreads,
  584 + _multiprocessing_func_wrapper, args)
592 585
593 # Construct results 586 # Construct results
594 for i in range(self.obs().size()): 587 for i in range(self.obs().size()):
cscripts/csphasecrv.py
@@ -493,20 +493,13 @@ class csphasecrv(ctools.csobservation): @@ -493,20 +493,13 @@ class csphasecrv(ctools.csobservation):
493 # Write header 493 # Write header
494 self._log_header1(gammalib.TERSE, 'Generate phase curve') 494 self._log_header1(gammalib.TERSE, 'Generate phase curve')
495 495
496 - # If using multiprocessing 496 + # If more than a single thread is requested then use multiprocessing
497 if self._nthreads > 1: 497 if self._nthreads > 1:
498 498
499 - # Create pool of workers  
500 - from multiprocessing import Pool  
501 - pool = Pool(processes = self._nthreads)  
502 -  
503 - # Run time bin analysis in parallel with map 499 + # Compute phase bins
504 args = [(self, phbin) for phbin in self._phbins] 500 args = [(self, phbin) for phbin in self._phbins]
505 - poolresults = pool.map(_multiprocessing_func_wrapper, args)  
506 -  
507 - # Close pool and join  
508 - pool.close()  
509 - pool.join() 501 + poolresults = mputils.process(self._nthreads,
  502 + _multiprocessing_func_wrapper, args)
510 503
511 # Construct results 504 # Construct results
512 for i in range(len(self._phbins)): 505 for i in range(len(self._phbins)):
cscripts/cspull.py
@@ -377,40 +377,34 @@ class cspull(ctools.csobservation): @@ -377,40 +377,34 @@ class cspull(ctools.csobservation):
377 # Write header 377 # Write header
378 self._log_header1(gammalib.TERSE, 'Generate pull distribution') 378 self._log_header1(gammalib.TERSE, 'Generate pull distribution')
379 379
380 - # If using multiprocessing  
381 - if self._nthreads > 1:  
382 -  
383 - # Create pool of workers  
384 - from multiprocessing import Pool  
385 - pool = Pool(processes = self._nthreads)  
386 -  
387 - # Run time bin analysis in parallel with map  
388 - args = [(self, seed + self['seed'].integer())  
389 - for seed in range(self['ntrials'].integer())]  
390 - poolresults = pool.map(_multiprocessing_func_wrapper, args) 380 + # Get number of trials
  381 + ntrials = self['ntrials'].integer()
391 382
392 - # Close pool and join  
393 - pool.close()  
394 - pool.join() 383 + # Get seed value
  384 + seed = self['seed'].integer()
395 385
396 - # Write results  
397 - for seed in range(self['ntrials'].integer()):  
398 - result = poolresults[seed][0]  
399 - ioutils.write_csv_row(self['outfile'].filename().url(), seed,  
400 - result['colnames'], result['values'])  
401 - self._log_string(gammalib.TERSE, poolresults[seed][1]['log'], False) 386 + # If more than a single thread is requested then use multiprocessing
  387 + if self._nthreads > 1:
  388 + args = [(self, i + seed) for i in range(ntrials)]
  389 + poolresults = mputils.process(self._nthreads,
  390 + _multiprocessing_func_wrapper, args)
402 391
403 - # Otherwise, loop over trials  
404 - else: 392 + # Continue with regular processing
  393 + for i in range(ntrials):
405 394
406 - for seed in range(self['ntrials'].integer()): 395 + # If multiprocessing was used then recover results and put them
  396 + # into the log file
  397 + if self._nthreads > 1:
  398 + result = poolresults[i][0]
  399 + self._log_string(gammalib.TERSE, poolresults[i][1]['log'], False)
407 400
408 - # Make a trial and add initial seed  
409 - result = self._trial(seed + self['seed'].integer()) 401 + # ... otherwise make a trial now
  402 + else:
  403 + result = self._trial(i + seed)
410 404
411 - # Write out trial result  
412 - ioutils.write_csv_row(self['outfile'].filename().url(), seed,  
413 - result['colnames'], result['values']) 405 + # Write out trial result
  406 + ioutils.write_csv_row(self['outfile'].filename().url(), i,
  407 + result['colnames'], result['values'])
414 408
415 # Return 409 # Return
416 return 410 return
cscripts/cssens.py
@@ -715,17 +715,10 @@ class cssens(ctools.csobservation): @@ -715,17 +715,10 @@ class cssens(ctools.csobservation):
715 # If using multiprocessing 715 # If using multiprocessing
716 if self._nthreads > 1 and self._ebounds.size() > 1: 716 if self._nthreads > 1 and self._ebounds.size() > 1:
717 717
718 - # Create pool of workers  
719 - from multiprocessing import Pool  
720 - pool = Pool(processes = self._nthreads)  
721 -  
722 - # Run energy bin analysis in parallel with map 718 + # Compute phase bins
723 args = [(self, ieng) for ieng in range(self._ebounds.size())] 719 args = [(self, ieng) for ieng in range(self._ebounds.size())]
724 - poolresults = pool.map(_multiprocessing_func_wrapper, args)  
725 -  
726 - # Close pool and join  
727 - pool.close()  
728 - pool.join() 720 + poolresults = mputils.process(self._nthreads,
  721 + _multiprocessing_func_wrapper, args)
729 722
730 # Construct results 723 # Construct results
731 for ieng in range(self._ebounds.size()): 724 for ieng in range(self._ebounds.size()):
cscripts/csspec.py
@@ -771,20 +771,13 @@ class csspec(ctools.csobservation): @@ -771,20 +771,13 @@ class csspec(ctools.csobservation):
771 # Initialise results 771 # Initialise results
772 results = [] 772 results = []
773 773
774 - # If using multiprocessing 774 + # If more than a single thread is requested then use multiprocessing
775 if self._nthreads > 1: 775 if self._nthreads > 1:
776 776
777 - # Create pool of workers  
778 - from multiprocessing import Pool  
779 - pool = Pool(processes = self._nthreads)  
780 -  
781 - # Run energy bin analysis in parallel with map 777 + # Compute energy bins
782 args = [(self, i) for i in range(self._ebounds.size())] 778 args = [(self, i) for i in range(self._ebounds.size())]
783 - poolresults = pool.map(_multiprocessing_func_wrapper, args)  
784 -  
785 - # Close pool and join  
786 - pool.close()  
787 - pool.join() 779 + poolresults = mputils.process(self._nthreads,
  780 + _multiprocessing_func_wrapper, args)
788 781
789 # Construct results 782 # Construct results
790 for i in range(self._ebounds.size()): 783 for i in range(self._ebounds.size()):
cscripts/cstsdist.py
@@ -329,38 +329,30 @@ class cstsdist(ctools.csobservation): @@ -329,38 +329,30 @@ class cstsdist(ctools.csobservation):
329 # Write header 329 # Write header
330 self._log_header1(gammalib.TERSE, 'Generate TS distribution') 330 self._log_header1(gammalib.TERSE, 'Generate TS distribution')
331 331
332 - # If using multiprocessing  
333 - if self._nthreads > 1:  
334 -  
335 - # Create pool of workers  
336 - from multiprocessing import Pool  
337 - pool = Pool(processes = self._nthreads)  
338 -  
339 - # Run time bin analysis in parallel with map  
340 - args = [(self, seed) for seed in range(self['ntrials'].integer())]  
341 - poolresults = pool.map(_multiprocessing_func_wrapper, args) 332 + # Get number of trials
  333 + ntrials = self['ntrials'].integer()
342 334
343 - # Close pool and join  
344 - pool.close()  
345 - pool.join()  
346 -  
347 - # Write results  
348 - for seed in range(self['ntrials'].integer()):  
349 - result = poolresults[seed][0]  
350 - ioutils.write_csv_row(self['outfile'].filename().url(), seed,  
351 - result['colnames'], result['values'])  
352 - self._log_string(gammalib.TERSE, poolresults[seed][1]['log'], False) 335 + # If more than a single thread is requested then use multiprocessing
  336 + if self._nthreads > 1:
  337 + args = [(self, i) for i in range(ntrials)]
  338 + poolresults = mputils.process(self._nthreads,
  339 + _multiprocessing_func_wrapper, args)
353 340
354 - # Otherwise, loop over trials  
355 - else: 341 + # Continue with regular processing
  342 + for i in range(ntrials):
356 343
357 - for seed in range(self['ntrials'].integer()): 344 + # If multiprocessing was used then recover results and put them
  345 + # into the log file
  346 + if self._nthreads > 1:
  347 + result = poolresults[i][0]
  348 + self._log_string(gammalib.TERSE, poolresults[i][1]['log'], False)
358 349
359 - # Make a trial  
360 - result = self._trial(seed) 350 + # ... otherwise make a trial now
  351 + else:
  352 + result = self._trial(i)
361 353
362 - # Write out trial result  
363 - ioutils.write_csv_row(self['outfile'].filename().url(), seed, 354 + # Write out trial result
  355 + ioutils.write_csv_row(self['outfile'].filename().url(), i,
364 result['colnames'], result['values']) 356 result['colnames'], result['values'])
365 357
366 # Return 358 # Return
cscripts/mputils.py
@@ -20,12 +20,12 @@ @@ -20,12 +20,12 @@
20 import gammalib 20 import gammalib
21 21
22 22
23 -# ==================== #  
24 -# Number of processes #  
25 -# ==================== # 23 +# ====================== #
  24 +# Get number of threads #
  25 +# ====================== #
26 def nthreads(cls): 26 def nthreads(cls):
27 """ 27 """
28 - Determines the number of parallel processes to use. 28 + Determines the number of parallel processes to use
29 29
30 The number is based on the user parameter "nthreads" and the availability 30 The number is based on the user parameter "nthreads" and the availability
31 of the multiprocessing module. 31 of the multiprocessing module.
@@ -38,7 +38,7 @@ def nthreads(cls): @@ -38,7 +38,7 @@ def nthreads(cls):
38 Returns 38 Returns
39 ------- 39 -------
40 nthreads : int 40 nthreads : int
41 - Number of parallel processes 41 + Number of threads
42 """ 42 """
43 # Log multiprocessing configuration 43 # Log multiprocessing configuration
44 cls._log_header1(gammalib.NORMAL, 'Multiprocessing') 44 cls._log_header1(gammalib.NORMAL, 'Multiprocessing')
@@ -69,3 +69,41 @@ def nthreads(cls): @@ -69,3 +69,41 @@ def nthreads(cls):
69 69
70 # Return number of processes to be used 70 # Return number of processes to be used
71 return nthreads 71 return nthreads
  72 +
  73 +
  74 +# ============================ #
  75 +# Execute function in parallel #
  76 +# ============================ #
  77 +def process(nthreads, function, args):
  78 + """
  79 + Execute function in parallel
  80 +
  81 + Parameters
  82 + ----------
  83 + nthreads : int
  84 + Number of parallel threads
  85 + function : Python function
  86 + Function to be executed in parallel
  87 + args : list of tuples
  88 + Function arguments for each evaluation
  89 +
  90 + Returns
  91 + -------
  92 + results : list of dicts
  93 + List of function evaluation results
  94 + """
  95 + # Import Pool module from multiprocessing module
  96 + from multiprocessing import Pool
  97 +
  98 + # Create a pool of processes
  99 + pool = Pool(processes=nthreads)
  100 +
  101 + # Run function in parallel
  102 + results = pool.map(function, args)
  103 +
  104 + # Close pool and join
  105 + pool.close()
  106 + pool.join()
  107 +
  108 + # Return results
  109 + return results
test/test_ctbutterfly.py
@@ -123,6 +123,12 @@ class Test(test): @@ -123,6 +123,12 @@ class Test(test):
123 # Check result file 123 # Check result file
124 self._check_result_file('ctbutterfly_py1.dat') 124 self._check_result_file('ctbutterfly_py1.dat')
125 125
  126 + # Check CSV result
  127 + self.test_value(butterfly.butterfly().nrows(), 100,
  128 + 'Check for 100 rows in CSV file')
  129 + self.test_value(butterfly.butterfly().ncols(), 4,
  130 + 'Check for 4 columns in CSV file')
  131 +
126 # Set-up ctbutterfly 132 # Set-up ctbutterfly
127 butterfly = ctools.ctbutterfly() 133 butterfly = ctools.ctbutterfly()
128 butterfly['inobs'] = self._events 134 butterfly['inobs'] = self._events