1717from ..gpu import gpu_utils as gpu_utils
1818from ..general import decorators as decorators
1919
20+ from ..cobra_functions import stats as cp
21+
22+ # from .. import cobra_functions.stats.calc_cell_stats as calc_cell_stats
23+
2024
2125class Monitor (Printing ):
2226 """ Abstract base class for monitors. A monitor can request
@@ -122,9 +126,9 @@ def _create_file_structure(self, parameters_dict):
122126 h5group .create_dataset (stats , shape = (self .n_steps ,),
123127 compression = 'gzip' , compression_opts = 9 )
124128 h5file .close ()
125- except :
126- self .prints ( 'Creation of bunch monitor file ' + self . filename +
127- 'failed. \n ' )
129+ except Exception as err :
130+ self .warns ( 'Problem occurred during Bunch monitor creation.' )
131+ self . warns ( err . message )
128132 raise
129133
130134 @decorators .synchronize_gpu_streams_after
@@ -296,9 +300,9 @@ def _create_file_structure(self, parameters_dict):
296300 h5group_slice .create_dataset (stats , shape = (self .slicer .n_slices ,
297301 self .n_steps ), compression = 'gzip' , compression_opts = 9 )
298302 h5file .close ()
299- except :
300- self .prints ( 'Creation of slice monitor file ' + self . filename +
301- 'failed. \n ' )
303+ except Exception as err :
304+ self .warns ( 'Problem occurred during Slice monitor creation.' )
305+ self . warns ( err . message )
302306 raise
303307
304308 def _write_data_to_buffer (self , bunch ):
@@ -398,7 +402,7 @@ def __init__(self, filename, stride=1, parameters_dict=None,
398402
399403 Optionally pass a list called quantities_to_store which
400404 specifies which members of the bunch will be called/stored.
401- """
405+ """
402406 quantities_to_store = [ 'x' , 'xp' , 'y' , 'yp' , 'z' , 'dp' , 'id' ]
403407 self .quantities_to_store = kwargs .pop ('quantities_to_store' ,
404408 quantities_to_store )
@@ -424,9 +428,9 @@ def _create_file_structure(self, parameters_dict):
424428 for key in parameters_dict :
425429 h5file .attrs [key ] = parameters_dict [key ]
426430 h5file .close ()
427- except :
428- self .prints ( 'Creation of particle monitor file ' +
429- self .filename + 'failed. \n ' )
431+ except Exception as err :
432+ self .warns ( 'Problem occurred during Particle monitor creation.' )
433+ self .warns ( err . message )
430434 raise
431435
432436 def _write_data_to_file (self , bunch , arrays_dict ):
@@ -465,3 +469,145 @@ def _write_data_to_file(self, bunch, arrays_dict):
465469 h5group [quant ][:] = quant_values [::self .stride ]
466470
467471 h5file .close ()
472+
473+
474+ class CellMonitor (Monitor ):
475+ """ Class to store cell (z, dp) specific data (for the moment only
476+ mean_x, mean_y, mean_z, mean_dp and n_particles_in_cell) to a HDF5
477+ file. This monitor uses a buffer (shift register) to reduce the
478+ number of writing operations to file. This also helps to avoid IO
479+ errors and loss of data when writing to a file that may become
480+ temporarily unavailable (e.g. if file is located on network) during
481+ the simulation. """
482+
483+ def __init__ (self , filename , n_steps , n_azimuthal_slices , n_radial_slices ,
484+ radial_cut , beta_z , parameters_dict = None ,
485+ write_buffer_every = 512 , buffer_size = 4096 , * args , ** kwargs ):
486+ """ Create an instance of a CellMonitor class. Apart from
487+ initializing the HDF5 file, a buffer self.buffer_cell is
488+ prepared to buffer the cell-specific data before writing them
489+ to file.
490+
491+ filename: Path and name of HDF5 file. Without file
492+ extension.
493+ n_steps: Number of entries to be reserved for each
494+ of the quantities in self.stats_to_store.
495+ n_azimuthal_slices: Number of pizza slices (azimuthal slicing).
496+ n_radial_slices: Number of rings (radial slicing).
497+ radial_cut: 'Radius' of the outermost ring in
498+ longitudinal phase space (using beta_z*dp)
499+ parameters_dict: Metadata for HDF5 file containing main
500+ simulation parameters.
501+ write_buffer_every: Number of steps after which buffer
502+ contents are actually written to file.
503+ buffer_size: Number of steps to be buffered. """
504+ stats_to_store = [
505+ 'mean_x' , 'mean_xp' ,
506+ 'mean_y' , 'mean_yp' ,
507+ 'mean_z' , 'mean_dp' ,
508+ 'macroparticlenumber' ]
509+ self .stats_to_store = kwargs .pop ('stats_to_store' , stats_to_store )
510+
511+ self .filename = filename
512+ self .n_steps = n_steps
513+ self .i_steps = 0
514+ self .n_azimuthal_slices = n_azimuthal_slices
515+ self .n_radial_slices = n_radial_slices
516+ self .radial_cut = radial_cut
517+ self .beta_z = beta_z
518+
519+ # Prepare buffer.
520+ self .buffer_size = buffer_size
521+ self .write_buffer_every = write_buffer_every
522+ self .buffer_cell = {}
523+
524+ for stats in self .stats_to_store :
525+ self .buffer_cell [stats ] = (
526+ np .zeros ((self .n_azimuthal_slices , self .n_radial_slices , self .buffer_size )))
527+ self ._create_file_structure (parameters_dict )
528+
529+ def dump (self , bunch ):
530+ """ Evaluate the statistics for the given cells and write the
531+ data to the buffer and/or to the HDF5 file. The buffer is used
532+ to reduce the number of writing operations to file. This helps
533+ to avoid IO errors and loss of data when writing data to a file
534+ that may become temporarily unavailable (e.g. if file is on
535+ network) during the simulation. Buffer contents are written to
536+ file only every self.write_buffer_every steps. """
537+ self ._write_data_to_buffer (bunch )
538+ if ((self .i_steps + 1 ) % self .write_buffer_every == 0 or
539+ (self .i_steps + 1 ) == self .n_steps ):
540+ self ._write_buffer_to_file ()
541+ self .i_steps += 1
542+
543+ def _create_file_structure (self , parameters_dict ):
544+ """ Initialize HDF5 file and create its basic structure (groups
545+ and datasets). One dataset for each of the quantities defined
546+ in self.stats_to_store is generated. If specified by
547+ the user, write the contents of the parameters_dict as metadata
548+ (attributes) to the file. Maximum file compression is
549+ activated. """
550+ try :
551+ h5file = hp .File (self .filename + '.h5' , 'w' )
552+ if parameters_dict :
553+ for key in parameters_dict :
554+ h5file .attrs [key ] = parameters_dict [key ]
555+
556+ h5file .create_group ('Cells' )
557+ h5group_cells = h5file ['Cells' ]
558+ for stats in self .stats_to_store :
559+ h5group_cells .create_dataset (
560+ stats , compression = 'gzip' , compression_opts = 9 ,
561+ shape = (self .n_azimuthal_slices , self .n_radial_slices ,
562+ self .n_steps ))
563+ h5file .close ()
564+ except Exception as err :
565+ self .warns ('Problem occurred during Cell monitor creation.' )
566+ self .warns (err .message )
567+ raise
568+
569+ def _write_data_to_buffer (self , bunch ):
570+ """ Store the data in the self.buffer dictionary before writing
571+ them to file. The buffer is implemented as a shift register. The
572+ cell-specific data are computed by a cython function. """
573+
574+ from PyHEADTAIL .cobra_functions .stats import calc_cell_stats
575+ n_cl , x_cl , xp_cl , y_cl , yp_cl , z_cl , dp_cl = calc_cell_stats (
576+ bunch , self .beta_z , self .radial_cut , self .n_radial_slices , self .n_azimuthal_slices )
577+
578+ self .buffer_cell ['mean_x' ][:,:,0 ] = x_cl [:,:]
579+ self .buffer_cell ['mean_xp' ][:,:,0 ] = xp_cl [:,:]
580+ self .buffer_cell ['mean_y' ][:,:,0 ] = y_cl [:,:]
581+ self .buffer_cell ['mean_yp' ][:,:,0 ] = yp_cl [:,:]
582+ self .buffer_cell ['mean_z' ][:,:,0 ] = z_cl [:,:]
583+ self .buffer_cell ['mean_dp' ][:,:,0 ] = dp_cl [:,:]
584+ self .buffer_cell ['macroparticlenumber' ][:,:,0 ] = n_cl [:,:]
585+
586+ for stats in self .stats_to_store :
587+ self .buffer_cell [stats ] = np .roll (
588+ self .buffer_cell [stats ], shift = - 1 , axis = 2 )
589+
590+ def _write_buffer_to_file (self ):
591+ """ Write buffer contents to the HDF5 file. The file is opened
592+ and closed each time the buffer is written to file to prevent
593+ from loss of data in case of a crash. """
594+ # Keep track of where to read from buffers and where to store
595+ # data in file.
596+ n_entries_in_buffer = min (self .i_steps + 1 , self .buffer_size )
597+ low_pos_in_buffer = self .buffer_size - n_entries_in_buffer
598+ low_pos_in_file = self .i_steps + 1 - n_entries_in_buffer
599+ up_pos_in_file = self .i_steps + 1
600+
601+ # Try to write data to file. If file is not available, skip this
602+ # step and repeat it again after self.write_buffer_every. As
603+ # long as self.buffer_size is not exceeded, no data are lost.
604+ try :
605+ h5file = hp .File (self .filename + '.h5' , 'a' )
606+ h5group_cells = h5file ['Cells' ]
607+
608+ for stats in self .stats_to_store :
609+ h5group_cells [stats ][:,:,low_pos_in_file :up_pos_in_file ] = \
610+ self .buffer_cell [stats ][:,:,low_pos_in_buffer :]
611+ h5file .close ()
612+ except Exception as err :
613+ self .warns (err .message )
0 commit comments