From eba56ed77960ac2af981683a29e9fc4710a88812 Mon Sep 17 00:00:00 2001
From: Gamelin Alexis <gamelin@synchrotron-soleil.fr>
Date: Sat, 2 May 2020 12:10:11 +0200
Subject: [PATCH] Add ProfileMonitor and misc on monitors.py

Add ProfileMonitor to save bunch profile
Add a track_bunch_data to Monitor class to group the code of track methods from BunchMonitor and PhaseSpaceMonitor
Implement BeamMonitor for the case without mpi
Remove hard coded h=416 ! in BeamMonitor
---
 tracking/monitors/monitors.py | 262 +++++++++++++++++++++++++++-------
 1 file changed, 212 insertions(+), 50 deletions(-)

diff --git a/tracking/monitors/monitors.py b/tracking/monitors/monitors.py
index af79efa..14ea801 100644
--- a/tracking/monitors/monitors.py
+++ b/tracking/monitors/monitors.py
@@ -46,6 +46,8 @@ class Monitor(Element, metaclass=ABCMeta):
         Close the HDF5 file shared by all Monitor subclass, must be called 
         by at least an instance of a Montior subclass at the end of the 
         tracking.
+    track_bunch_data(object_to_save)
+        Track method to use when saving bunch data.
     """
     
     _file_name_storage = []
@@ -200,6 +202,28 @@ class Monitor(Element, metaclass=ABCMeta):
             self.file.close()
         except ValueError:
             pass
+        
+    def track_bunch_data(self, object_to_save):
+        """
+        Track method to use when saving bunch data.
+        
+        Parameters
+        ----------
+        object_to_save : Beam or Bunch
+        """
+        if self.track_count % self.save_every == 0:
+            if isinstance(object_to_save, Beam):
+                if (object_to_save.mpi_switch == True):
+                    if object_to_save.mpi.bunch_num == self.bunch_number:
+                        self.to_buffer(object_to_save[object_to_save.mpi.bunch_num])
+                else:
+                    self.to_buffer(object_to_save[self.bunch_number])
+            elif isinstance(object_to_save, Bunch):
+                self.to_buffer(object_to_save)
+            else:                            
+                raise TypeError("object_to_save should be a Beam or Bunch object.")
+        self.track_count += 1
+        
             
 class BunchMonitor(Monitor):
     """
@@ -257,18 +281,7 @@ class BunchMonitor(Monitor):
         ----------
         object_to_save : Bunch or Beam object
         """        
-        if self.track_count % self.save_every == 0:
-            if isinstance(object_to_save, Beam):
-                if (object_to_save.mpi_switch == True):
-                    if object_to_save.mpi.bunch_num == self.bunch_number:
-                        self.to_buffer(object_to_save[object_to_save.mpi.bunch_num])
-                else:
-                    self.to_buffer(object_to_save[self.bunch_number])
-            elif isinstance(object_to_save, Bunch):
-                self.to_buffer(object_to_save)
-            else:                            
-                raise TypeError("object_to_save should be a Beam or Bunch object.")
-        self.track_count += 1
+        self.track_bunch_data(object_to_save)
 
             
 class PhaseSpaceMonitor(Monitor):
@@ -329,20 +342,8 @@ class PhaseSpaceMonitor(Monitor):
         Parameters
         ----------
         object_to_save : Bunch or Beam object
-        """
-        
-        if self.track_count % self.save_every == 0:
-            if isinstance(object_to_save, Beam):
-                if (object_to_save.mpi_switch == True):
-                    if object_to_save.mpi.bunch_num == self.bunch_number:
-                        self.to_buffer(object_to_save[object_to_save.mpi.bunch_num])
-                else:
-                    self.to_buffer(object_to_save[self.bunch_number])
-            elif isinstance(object_to_save, Bunch):
-                self.to_buffer(object_to_save)
-            else:                            
-                raise TypeError("object_to_save should be a Beam or Bunch object.")
-        self.track_count += 1
+        """        
+        self.track_bunch_data(object_to_save)
 
             
 class BeamMonitor(Monitor):
@@ -377,24 +378,21 @@ class BeamMonitor(Monitor):
         Save data    
     """
     
-    def __init__(self, file_name=None, save_every=5, buffer_size=500, 
+    def __init__(self, h, file_name=None, save_every=5, buffer_size=500, 
                  total_size=2e4, mpi_mode=True):
         
         group_name = "Beam"
-        dict_buffer = {}
-        dict_file = {}
+        dict_buffer = {"mean" : (6, h, buffer_size), 
+                       "std" : (6, h, buffer_size),
+                       "emit" : (3, h, buffer_size),
+                       "current" : (h, buffer_size)}
+        dict_file = {"mean" : (6, h, total_size), 
+                       "std" : (6, h, total_size),
+                       "emit" : (3, h, total_size),
+                       "current" : (h, total_size)}
+        
         self.monitor_init(group_name, save_every, buffer_size, total_size,
                           dict_buffer, dict_file, file_name, mpi_mode)
-        
-        self.mean = np.zeros((6, self.buffer_size), dtype=float)
-        self.std = np.zeros((6, self.buffer_size), dtype=float)
-        self.emit = np.zeros((3, self.buffer_size), dtype=float)
-        self.current = np.zeros((self.buffer_size,), dtype=float)
-        
-        self.g.require_dataset("mean", (6, 416, self.total_size,), dtype=float)
-        self.g.require_dataset("std", (6, 416, self.total_size,), dtype=float)
-        self.g.require_dataset("emit", (3, 416, self.total_size,), dtype=float)
-        self.g.require_dataset("current", (416, self.total_size,), dtype=float)
                     
     def track(self, beam):
         """
@@ -408,13 +406,13 @@ class BeamMonitor(Monitor):
             if (beam.mpi_switch == True):
                 self.to_buffer(beam[beam.mpi.bunch_num], beam.mpi.bunch_num)
             else:
-                raise NotImplementedError
+                self.to_buffer_no_mpi(beam)
                     
         self.track_count += 1
         
     def to_buffer(self, bunch, bunch_num):
         """
-        Save data to buffer.
+        Save data to buffer, if mpi is being used.
         
         Parameters
         ----------
@@ -423,20 +421,41 @@ class BeamMonitor(Monitor):
         """
         
         self.time[self.buffer_count] = self.track_count
-        self.mean[:, self.buffer_count] = bunch.mean
-        self.std[:, self.buffer_count] = bunch.std
-        self.emit[:, self.buffer_count] = bunch.emit
-        self.current[self.buffer_count] = bunch.current
+        self.mean[:, bunch_num, self.buffer_count] = bunch.mean
+        self.std[:, bunch_num, self.buffer_count] = bunch.std
+        self.emit[:, bunch_num, self.buffer_count] = bunch.emit
+        self.current[bunch_num, self.buffer_count] = bunch.current
         
         self.buffer_count += 1
         
         if self.buffer_count == self.buffer_size:
             self.write(bunch_num)
             self.buffer_count = 0
+            
+    def to_buffer_no_mpi(self, beam):
+        """
+        Save data to buffer, if mpi is not being used.
+        
+        Parameters
+        ----------
+        beam : Beam object
+        """
+              
+        self.time[self.buffer_count] = self.track_count
+        self.mean[:, :, self.buffer_count] = beam.bunch_mean
+        self.std[:, :, self.buffer_count] = beam.bunch_std
+        self.emit[:, :, self.buffer_count] = beam.bunch_emit
+        self.current[:, self.buffer_count] = beam.bunch_current
+        
+        self.buffer_count += 1
+        
+        if self.buffer_count == self.buffer_size:
+            self.write_no_mpi()
+            self.buffer_count = 0
 
     def write(self, bunch_num):
         """
-        Write data from buffer to the HDF5 file.
+        Write data from buffer to the HDF5 file, if mpi is being used.
         
         Parameters
         ----------
@@ -447,21 +466,164 @@ class BeamMonitor(Monitor):
     
         self.file[self.group_name]["mean"][:, bunch_num, 
                  self.write_count*self.buffer_size:(self.write_count+1) * 
-                 self.buffer_size] = self.mean
+                 self.buffer_size] = self.mean[:, bunch_num, :]
                  
         self.file[self.group_name]["std"][:, bunch_num, 
                  self.write_count*self.buffer_size:(self.write_count+1) * 
-                 self.buffer_size] = self.std
+                 self.buffer_size] = self.std[:, bunch_num, :]
 
         self.file[self.group_name]["emit"][:, bunch_num, 
                  self.write_count*self.buffer_size:(self.write_count+1) * 
-                 self.buffer_size] = self.emit
+                 self.buffer_size] = self.emit[:, bunch_num, :]
 
         self.file[self.group_name]["current"][bunch_num, 
+                 self.write_count*self.buffer_size:(self.write_count+1) * 
+                 self.buffer_size] = self.current[bunch_num, :]
+                 
+        self.file.flush() 
+        self.write_count += 1
+        
+    def write_no_mpi(self):
+        """
+        Write data from buffer to the HDF5 file, if mpi is not being used.
+        """
+        
+        self.file[self.group_name]["time"][self.write_count*self.buffer_size:(
+                    self.write_count+1)*self.buffer_size] = self.time
+    
+        self.file[self.group_name]["mean"][:, :, 
+                 self.write_count*self.buffer_size:(self.write_count+1) * 
+                 self.buffer_size] = self.mean
+                 
+        self.file[self.group_name]["std"][:, :, 
+                 self.write_count*self.buffer_size:(self.write_count+1) * 
+                 self.buffer_size] = self.std
+
+        self.file[self.group_name]["emit"][:, :, 
+                 self.write_count*self.buffer_size:(self.write_count+1) * 
+                 self.buffer_size] = self.emit
+
+        self.file[self.group_name]["current"][:, 
                  self.write_count*self.buffer_size:(self.write_count+1) * 
                  self.buffer_size] = self.current
                  
         self.file.flush() 
         self.write_count += 1
+
+        
+class ProfileMonitor(Monitor):
+    """
+    Monitor a single bunch and save bunch profiles.
+    
+    Parameters
+    ----------
+    bunch_number : int
+        Bunch to monitor.
+    dimensions : str or list of str, optional
+        Dimensions to save.
+    n_bin : int or list of int, optional
+        Number of bin to use in each dimension.
+    file_name : string, optional
+        Name of the HDF5 where the data will be stored. Must be specified
+        the first time a subclass of Monitor is instancied and must be None
+        the following times.
+    save_every : int or float, optional
+        Set the frequency of the save. The data is saved every save_every 
+        call of the montior.
+    buffer_size : int or float, optional
+        Size of the save buffer.
+    total_size : int or float, optional
+        Total size of the save. The following relationships between the 
+        parameters must exist: 
+            total_size % buffer_size == 0
+            number of call to track / save_every == total_size
+    mpi_mode : bool, optional
+        If True, open the HDF5 file in parallel mode, which is needed to
+        allow several cores to write in the same file at the same time.
+        If False, open the HDF5 file in standard mode.
+
+    Methods
+    -------
+    track(object_to_save)
+        Save data
+    """
+    
+    def __init__(self, bunch_number, dimensions="tau", n_bin=75, file_name=None, 
+                 save_every=5, buffer_size=500, total_size=2e4, mpi_mode=True):
         
-        
\ No newline at end of file
+        self.bunch_number = bunch_number
+        group_name = "ProfileData_" + str(self.bunch_number)
+        
+        if isinstance(dimensions, str):
+            self.dimensions = [dimensions]
+        else:
+            self.dimensions = dimensions
+            
+        if isinstance(n_bin, int):
+            self.n_bin = np.ones((len(self.dimensions),), dtype=int)*n_bin
+        else:
+            self.n_bin = n_bin
+        
+        dict_buffer = {}
+        dict_file = {}
+        for index, dim in enumerate(dimensions):
+            dict_buffer.update({dim : (self.n_bin[index], buffer_size)})
+            dict_buffer.update({dim + "_bin" : (self.n_bin[index] + 1, buffer_size)})
+            dict_file.update({dim : (self.n_bin[index], total_size)})
+            dict_file.update({dim + "_bin" : (self.n_bin[index] + 1, total_size)})
+
+        self.monitor_init(group_name, save_every, buffer_size, total_size,
+                          dict_buffer, dict_file, file_name, mpi_mode)
+        
+        self.dict_buffer = dict_buffer
+        self.dict_file = dict_file
+        
+    def to_buffer(self, bunch):
+        """
+        Save data to buffer.
+        
+        Parameters
+        ----------
+        bunch : Bunch object
+        """
+
+        self.time[self.buffer_count] = self.track_count
+        for index, dim in enumerate(self.dimensions):
+            bins, sorted_index, profile = bunch.binning(dim, self.n_bin[index])
+            bin_array = np.append(np.array(bins.left), bins.right[-1])
+            profile_array = np.array(profile)
+            self.__getattribute__(dim + "_bin")[:, self.buffer_count] = bin_array
+            self.__getattribute__(dim)[:, self.buffer_count] = profile_array
+        
+        self.buffer_count += 1
+        
+        if self.buffer_count == self.buffer_size:
+            self.write()
+            self.buffer_count = 0
+            
+    def write(self):
+        """Write data from buffer to the HDF5 file."""
+        
+        self.file[self.group_name]["time"][self.write_count*self.buffer_size:(
+                    self.write_count+1)*self.buffer_size] = self.time
+
+        for dim in self.dimensions:
+            self.file[self.group_name][dim][:, 
+                    self.write_count * self.buffer_size:(self.write_count+1) * 
+                    self.buffer_size] = self.__getattribute__(dim)
+            self.file[self.group_name][dim + "_bin"][:, 
+                    self.write_count * self.buffer_size:(self.write_count+1) * 
+                    self.buffer_size] = self.__getattribute__(dim + "_bin")
+            
+        self.file.flush()
+        self.write_count += 1
+                    
+    def track(self, object_to_save):
+        """
+        Save data.
+        
+        Parameters
+        ----------
+        object_to_save : Bunch or Beam object
+        """        
+        self.track_bunch_data(object_to_save)
\ No newline at end of file
-- 
GitLab