From db49cb81bf895a0c1ae609012f9fc492ba085b52 Mon Sep 17 00:00:00 2001
From: Gamelin Alexis <alexis.gamelin@synchrotron-soleil.fr>
Date: Mon, 10 Jan 2022 15:20:26 +0100
Subject: [PATCH] Allow for particle losses in Monitor classes

Add a is_empty property to the Bunch class.
Add a check_empty optional parameter to the Monitor.track_bunch_data method.
ProfileMonitor: if the bunch is empty, no profile is saved.
WakePotentialMonitor: if the bunch is empty, the same from last good turn is saved.
BunchSpectrumMonitor: if the bunch is empty, the incoherent spectrum is nan.
---
 tracking/monitors/monitors.py | 26 +++++++++++++++++---------
 tracking/particles.py         |  7 +++++++
 2 files changed, 24 insertions(+), 9 deletions(-)

diff --git a/tracking/monitors/monitors.py b/tracking/monitors/monitors.py
index a543fdf..f0f8a4d 100644
--- a/tracking/monitors/monitors.py
+++ b/tracking/monitors/monitors.py
@@ -218,27 +218,32 @@ class Monitor(Element, metaclass=ABCMeta):
         except ValueError:
             pass
         
-    def track_bunch_data(self, object_to_save):
+    def track_bunch_data(self, object_to_save, check_empty=False):
         """
         Track method to use when saving bunch data.
         
         Parameters
         ----------
         object_to_save : Beam or Bunch
+        check_emptiness: bool
+            If True, check if the bunch is empty. If it is, then do nothing.
         """
         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])
+                    bunch = object_to_save[object_to_save.mpi.bunch_num]
                 else:
-                    self.to_buffer(object_to_save[self.bunch_number])
+                    bunch = object_to_save[self.bunch_number]
             elif isinstance(object_to_save, Bunch):
-                self.to_buffer(object_to_save)
+                bunch = object_to_save
             else:                            
                 raise TypeError("object_to_save should be a Beam or Bunch object.")
+                
+            if (check_empty == False) or (bunch.is_empty == False):
+                self.to_buffer(bunch)
+                
         self.track_count += 1
-        
             
 class BunchMonitor(Monitor):
     """
@@ -672,7 +677,7 @@ class ProfileMonitor(Monitor):
         ----------
         object_to_save : Bunch or Beam object
         """        
-        self.track_bunch_data(object_to_save)
+        self.track_bunch_data(object_to_save, check_empty=True)
         
 class WakePotentialMonitor(Monitor):
     """
@@ -1004,8 +1009,11 @@ class BunchSpectrumMonitor(Monitor):
             raise TypeError("object_to_save should be a Beam or Bunch object.")
         
         if skip is False:
-            for key, value in self.track_dict.items():
-                self.positions[value, :, self.save_count] = bunch[key][self.index_sample]
+            try:
+                for key, value in self.track_dict.items():
+                    self.positions[value, :, self.save_count] = bunch[key][self.index_sample]
+            except IndexError:
+                self.positions[value, :, self.save_count] = np.nan
             
             self.mean[:, self.save_count] = bunch.mean[self.mean_index]
             
diff --git a/tracking/particles.py b/tracking/particles.py
index e7939e8..f4351c1 100644
--- a/tracking/particles.py
+++ b/tracking/particles.py
@@ -76,6 +76,8 @@ class Bunch:
         Number of particles in the bunch.
     current : float
         Bunch current in [A].
+    is_empty : bool
+        Return True if the bunch is empty.
     mean : array of shape (6,)
         Mean position of alive particles for each coordinates.
     std : array of shape (6,)
@@ -192,6 +194,11 @@ class Bunch:
     @current.setter
     def current(self, value):
         self.charge_per_mp = value * self.ring.T0 / self.__len__()
+        
+    @property
+    def is_empty(self):
+        """Return True if the bunch is empty."""
+        return ~np.any(self.alive)
     
     @property    
     def mean(self):
-- 
GitLab