From ebd806ea915a09966b628cc1e7032565d88359c7 Mon Sep 17 00:00:00 2001
From: Gamelin Alexis <alexis.gamelin@synchrotron-soleil.fr>
Date: Tue, 11 Jul 2023 17:49:13 +0200
Subject: [PATCH] Add the interface for FB/loops in CavityResonator

Add an interface in CavityResonator to allow for external FB and loops.
Add the ProportionalLoop and TunerLoop using what was already done in the RF-FB branch.
---
 mbtrack2/tracking/__init__.py |   4 +-
 mbtrack2/tracking/rf.py       | 120 +++++++++++++++++++++++++++++++++-
 2 files changed, 121 insertions(+), 3 deletions(-)

diff --git a/mbtrack2/tracking/__init__.py b/mbtrack2/tracking/__init__.py
index 38a0f8d..2e53921 100644
--- a/mbtrack2/tracking/__init__.py
+++ b/mbtrack2/tracking/__init__.py
@@ -6,7 +6,9 @@ from mbtrack2.tracking.particles import (Electron,
                                          Particle)
 from mbtrack2.tracking.synchrotron import Synchrotron
 from mbtrack2.tracking.rf import (RFCavity, 
-                                  CavityResonator)
+                                  CavityResonator,
+                                  ProportionalLoop,
+                                  TunerLoop)
 from mbtrack2.tracking.parallel import Mpi
 from mbtrack2.tracking.element import (Element, 
                                        LongitudinalMap, 
diff --git a/mbtrack2/tracking/rf.py b/mbtrack2/tracking/rf.py
index 81d9a48..ad397ff 100644
--- a/mbtrack2/tracking/rf.py
+++ b/mbtrack2/tracking/rf.py
@@ -58,6 +58,9 @@ class CavityResonator():
     If used with mpi, beam.mpi.share_distributions must be called before the 
     track method call.
     
+    Different kind of RF feeback and loops can be added using:
+        cavity_resonator.feedback.append(loop)
+    
     Parameters
     ----------
     ring : Synchrotron object
@@ -212,6 +215,7 @@ class CavityResonator():
         self.theta_gr = 0
         self.Pg = 0
         self.n_bin = int(n_bin)
+        self.feedback = []
         
     def init_tracking(self, beam):
         """
@@ -318,10 +322,13 @@ class CavityResonator():
             
             # phasor decay to be at t=0 of the next bunch
             self.phasor_decay(self.ring.T1, ref_frame="beam")
+            
+        # apply different kind of RF feedback
+        for fb in self.feedback:
+            fb.track(self)
                 
         self.nturn += 1
                 
-        
     def init_phasor_track(self, beam):
         """
         Initialize the beam phasor for a given beam distribution using a
@@ -894,4 +901,113 @@ class CavityResonator():
         
     def deltaVRF(self, z, I0, F = 1, PHI = 0):
         """Return the generator voltage minus beam loading voltage taking into account form factor amplitude F and form factor phase PHI"""
-        return -1*self.Vg*(self.ring.k1*self.m)**2*np.cos(self.ring.k1*self.m*z + self.theta_g) - self.Vb(I0)*F*(self.ring.k1*self.m)**2*np.cos(self.ring.k1*self.m*z + self.psi - PHI)
\ No newline at end of file
+        return -1*self.Vg*(self.ring.k1*self.m)**2*np.cos(self.ring.k1*self.m*z + self.theta_g) - self.Vb(I0)*F*(self.ring.k1*self.m)**2*np.cos(self.ring.k1*self.m*z + self.psi - PHI)
+    
+class ProportionalLoop():
+    """
+    Proportional feedback loop to control a CavityResonator amplitude and phase.
+    
+    Feedback setpoints are cavity_resonator.Vc and cavity_resonator.theta.
+    
+    The loop must be added to the CavityResonator object using:
+        cavity_resonator.feedback.append(loop)
+
+    Parameters
+    ----------
+    ring : Synchrotron object
+    cavity_resonator : CavityResonator
+        The CavityResonator which is to be controlled.
+    gain_A : float
+        Amplitude (voltage) gain of the feedback.
+    gain_P : float
+        Phase gain of the feedback.
+    delay : int
+        Feedback delay in unit of turns.
+
+    """
+    def __init__(self, ring, cavity_resonator, gain_A, gain_P, delay):
+        self.ring = ring
+        self.gain_A = gain_A
+        self.gain_P = gain_P
+        self.delay = int(delay)
+        self.volt_delay = np.ones(self.delay)*cavity_resonator.Vc
+        self.phase_delay = np.ones(self.delay)*cavity_resonator.theta
+    
+    def track(self, cavity_resonator):
+        """
+        Tracking method for the amplitude and phase loop.
+
+        Returns
+        -------
+        None.
+
+        """
+        diff_A = self.volt_delay[-1] - cavity_resonator.Vc
+        diff_P = self.phase_delay[-1] - cavity_resonator.theta
+        cavity_resonator.Vg -= self.gain_A*diff_A
+        cavity_resonator.theta_g -= self.gain_P*diff_P
+        cavity_resonator.generator_phasor_record = np.ones(self.ring.h)*cavity_resonator.generator_phasor
+        self.volt_delay = np.roll(self.volt_delay, 1)
+        self.phase_delay = np.roll(self.phase_delay, 1)
+        self.volt_delay[0] = cavity_resonator.cavity_voltage
+        self.phase_delay[0] = cavity_resonator.cavity_phase
+
+class TunerLoop():
+    """
+    Cavity tuner loop used to control a CavityResonator tuning (psi or detune)
+    in order to keep the phase between cavity and generator current constant.
+    
+    Only a proportional controller is assumed.
+    
+    The loop must be added to the CavityResonator object using:
+        cavity_resonator.feedback.append(loop)
+
+    Parameters
+    ----------
+    ring : Synchrotron object
+    cavity_resonator : CavityResonator
+        The CavityResonator which is to be controlled.
+    gain : float
+        Proportional gain of the tuner loop.
+        If not specified, 0.01 is used.
+    avering_period:
+        Period during which the phase difference is monitored and averaged. 
+        Then the feedback correction is applied every avering_period turn.
+        Unit is turn number.
+        A value longer than one synchrotron period (1/fs) is recommended.
+        If not specified, 2-synchrotron period (2/fs) is used, although it is
+        too fast compared to the actual situation.
+    offset : float
+        Tuning offset in [rad].
+        
+    """
+    def __init__(self, ring, cavity_resonator, gain=0.01, avering_period=0, 
+                 offset=0):
+        self.ring = ring
+        if avering_period == 0:
+            fs = self.ring.synchrotron_tune(cavity_resonator.Vc)*self.ring.f1/self.ring.h
+            avering_period = 2/fs/self.ring.T0
+    
+        self.Pgain = gain
+        self.offset = offset
+        self.avering_period = int(avering_period)
+        self.diff = 0
+        self.count = 0
+    
+    def track(self, cavity_resonator):
+        """
+        Tracking method for the tuner loop.
+    
+        Returns
+        -------
+        None.
+    
+        """
+        if self.count == self.avering_period:
+            diff = self.diff/self.avering_period - self.offset
+            cavity_resonator.psi -= diff * self.Pgain
+            self.count = 0
+            self.diff = 0
+        else:
+            self.diff += cavity_resonator.cavity_phase - cavity_resonator.theta_g + cavity_resonator.psi
+            self.count += 1
-- 
GitLab