diff --git a/mbtrack2/tracking/__init__.py b/mbtrack2/tracking/__init__.py
index 02843e280f5cd945178b040b903c468b095765cc..fe95c6d605c281df813310b6f434a229d5bc268d 100644
--- a/mbtrack2/tracking/__init__.py
+++ b/mbtrack2/tracking/__init__.py
@@ -26,6 +26,14 @@ from mbtrack2.tracking.rf import (
     RFCavity,
     TunerLoop,
 )
+from mbtrack2.tracking.rfdipole import (
+    DipoleCavityResonator,
+    DipoleDirectFeedback,
+    DipoleProportionalIntegralLoop,
+    DipoleProportionalLoop,
+    DipoleRFCavity,
+    DipoleTunerLoop,
+)
 from mbtrack2.tracking.synchrotron import Synchrotron
 from mbtrack2.tracking.wakepotential import (
     LongRangeResistiveWall,
diff --git a/mbtrack2/tracking/parallel.py b/mbtrack2/tracking/parallel.py
index 58c45b116a85901e7cb173c58e0ac934805cffc7..bdd155002d72c857dcaee716c84d52b3319f2c51 100644
--- a/mbtrack2/tracking/parallel.py
+++ b/mbtrack2/tracking/parallel.py
@@ -143,7 +143,7 @@ class Mpi:
         else:
             return max(self.table[:, 0])
 
-    def share_distributions(self, beam, dimensions="tau", n_bin=75):
+    def share_distributions(self, beam, dimensions="tau", dipole_plane=None,n_bin=75):
         """
         Compute the bunch profiles and share it between the different bunches.
 
@@ -152,6 +152,9 @@ class Mpi:
         beam : Beam object
         dimension : str or list of str, optional
             Dimensions in which the binning is done. The default is "tau".
+        dipole_plane : str or list of str, optional
+            Use for transverse high-Q resonator. The default is None.
+            "x" or "y"
         n_bin : int or list of int, optional
             Number of bins. The default is 75.
 
@@ -179,10 +182,21 @@ class Mpi:
             if len(bunch) != 0:
                 bins, sorted_index, profile, center = bunch.binning(
                     dimension=dim, n_bin=n)
+                if dim == "tau" and dipole_plane is not None :
+                    dipole = np.zeros((n - 1, ), dtype=np.float64) 
+                    for i in range(n - 1):
+                        dipole[i] = bunch[dipole_plane][sorted_index == i].sum()
+                    dipole = np.divide(dipole, profile, out=np.zeros_like(dipole), where=profile!=0)
+                    #dipole = dipole / profile # here
+                    #dipole[np.isnan(dipole)] = 0
+                #if self.bunch_num == 0:
+                #    print(dipole)
             else:
                 sorted_index = None
                 profile = np.zeros((n - 1, ), dtype=np.int64)
                 center = np.zeros((n - 1, ), dtype=np.float64)
+                if dim == "tau" and dipole_plane is not None :
+                    dipole = np.zeros((n - 1, ), dtype=np.float64) 
                 if beam.filling_pattern[self.bunch_num] is True:
                     beam.update_filling_pattern()
                     beam.update_distance_between_bunches()
@@ -201,6 +215,13 @@ class Mpi:
 
             self.__setattr__(dim + "_sorted_index", sorted_index)
 
+            if dim == "tau" and dipole_plane is not None :
+                self.__setattr__("tau_dipole",
+                    np.empty((self.size, n - 1), dtype=np.float64))
+                self.comm.Allgather(
+                    [dipole, self.MPI.DOUBLE],
+                    [self.__getattribute__("tau_dipole"), self.MPI.DOUBLE])
+
     def share_means(self, beam):
         """
         Compute the bunch means and share it between the different bunches.
diff --git a/mbtrack2/tracking/rf.py b/mbtrack2/tracking/rf.py
index 3d37de9b07f80a2bb01472f405e282b334ce359c..33513c4cda904c9ccd83a91301de164e7aaa1a8e 100644
--- a/mbtrack2/tracking/rf.py
+++ b/mbtrack2/tracking/rf.py
@@ -334,6 +334,8 @@ class CavityResonator():
 
                         self.beam_phasor -= 2 * charge_per_mp * self.loss_factor * mp_per_bin
                         self.phasor_decay(bin_length, ref_frame="beam")
+                        #if self.bunch_index == 0 and i < 20:
+                        #    print("#",self.nturn,energy_change[ind])
 
                 # phasor decay to be at t=0 of the current bunch (=-1*bins[-1])
                 self.phasor_decay(-1 * (center[-1] + bin_length/2),
@@ -345,6 +347,8 @@ class CavityResonator():
 
             # save beam phasor value
             self.beam_phasor_record[index] = self.beam_phasor
+            #if self.bunch_index == 0 and self.nturn==4:
+            #    print("#",self.beam_phasor)
 
             # phasor decay to be at t=0 of the next bunch
             self.phasor_decay(self.ring.T1, ref_frame="beam")
@@ -1492,7 +1496,6 @@ class ProportionalIntegralLoop():
             T = self.ring.T1 * self.every
             alpha = np.cos(omega*T)-1
             tmp = alpha * alpha - 2 * alpha
-            print(tmp)
             if tmp > 0:
                 self.IIRcoef = alpha + np.sqrt(tmp)
             else:
diff --git a/mbtrack2/tracking/rfdipole.py b/mbtrack2/tracking/rfdipole.py
new file mode 100644
index 0000000000000000000000000000000000000000..adfcd424e7a8ebac85e026b9b718b5f0f4af2832
--- /dev/null
+++ b/mbtrack2/tracking/rfdipole.py
@@ -0,0 +1,1778 @@
+# -*- coding: utf-8 -*-
+"""
+This module handles radio-frequency (RF) cavitiy elements. 
+"""
+
+import matplotlib.patches as mpatches
+import matplotlib.pyplot as plt
+import numpy as np
+from matplotlib.legend_handler import HandlerPatch
+
+from mbtrack2.tracking.element import Element
+
+
+class DipoleRFCavity(Element):
+    """
+    Perfect RF cavity class for main and harmonic RF cavities.
+    Use cosine definition.
+    
+    Parameters
+    ----------
+    ring : Synchrotron object
+    m : int
+        Harmonic number of the cavity
+    Vc : float
+        Amplitude of cavity voltage [V/m]
+    theta : float
+        Phase of Cavity voltage
+    """
+    def __init__(self, ring, m, Vc, theta,plane="x",offset=0):
+        self.ring = ring
+        self.m = m
+        self.Vc = Vc
+        self.theta = theta
+        self.plane = plane
+        self.offset = offset
+
+    @Element.parallel
+    def track(self, bunch):
+        """
+        Tracking method for the element.
+        No bunch to bunch interaction, so written for Bunch objects and
+        @Element.parallel is used to handle Beam objects.
+        
+        Parameters
+        ----------
+        bunch : Bunch or Beam object
+        """
+        bunch["delta"] += (bunch["self.plane"] - self.offset) *self.Vc / self.ring.E0 * np.cos(
+            self.m * self.ring.omega1 * bunch["tau"] + self.theta)
+
+    def value(self, val):
+        return self.Vc / self.ring.E0 * np.cos(self.m * self.ring.omega1 *
+                                               val + self.theta)
+
+
+class DipoleCavityResonator():
+    """Cavity resonator class for active or passive RF cavity with beam
+    loading or HOM, based on [1,2].
+    
+    Use cosine definition.
+    
+    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
+    m : int or float
+        Harmonic number of the cavity.
+    Rs : float
+        Shunt impedance of the cavities in [Ohm], defined as 0.5*Vc*Vc/Pc.
+        If Ncav = 1, used for the total shunt impedance.
+        If Ncav > 1, used for the shunt impedance per cavity.
+    Q : float
+        Quality factor of the cavity.
+    QL : float
+        Loaded quality factor of the cavity.
+    detune : float
+        Detuing of the cavity in [Hz], defined as (fr - m*ring.f1).
+    Ncav : int, optional
+        Number of cavities.
+    Vc : float, optinal
+        Total cavity voltage objective value in [V/m].
+    theta : float, optional
+        Total cavity phase objective value in [rad].
+    n_bin : int, optional
+        Number of bins used for the beam loading computation. 
+        Only used if MPI is not used, otherwise n_bin must be specified in the 
+        beam.mpi.share_distributions method.
+        The default is 75.
+        
+    Attributes
+    ----------
+    beam_phasor : complex
+        Beam phasor in [V].
+    beam_phasor_record : array of complex
+        Last beam phasor value of each bunch in [V].
+    generator_phasor : complex
+        Generator phasor in [V].
+    generator_phasor_record : array of complex
+        Last generator phasor value of each bunch in [V].
+    cavity_phasor : complex
+        Cavity phasor in [V].
+    cavity_phasor_record : array of complex
+        Last cavity phasor value of each bunch in [V].
+    ig_phasor_record : array of complex
+        Last current generator phasor of each bunch in [A].
+        Only used for some feedback types.
+    cavity_voltage : float
+        Cavity total voltage in [V].
+    cavity_phase : float
+        Cavity total phase in [rad].
+    loss_factor : float
+        Cavity loss factor in [V/C].
+    Rs_per_cavity : float
+        Shunt impedance of a single cavity in [Ohm], defined as 0.5*Vc*Vc/Pc.
+    beta : float
+        Coupling coefficient of the cavity.
+    fr : float
+        Resonance frequency of the cavity in [Hz].
+    wr : float
+        Angular resonance frequency in [Hz.rad].
+    psi : float
+        Tuning angle in [rad].
+    filling_time : float
+        Cavity filling time in [s].
+    Pc : float
+        Power dissipated in the cavity walls in [W].
+    Pg : float
+        Generator power in [W].
+    Vgr : float
+        Generator voltage at resonance in [V].
+    theta_gr : float
+        Generator phase at resonance in [rad].
+    Vg : float
+        Generator voltage in [V].
+    theta_g : float
+        Generator phase in [rad].
+    tracking : bool
+        True if the tracking has been initialized.
+    bunch_index : int
+        Number of the tracked bunch in the current core.
+    distance : array
+        Distance between bunches.
+    valid_bunch_index : array
+    
+    Methods
+    -------
+    Vbr(I0)
+        Return beam voltage at resonance in [V].
+    Vb(I0)
+        Return beam voltage in [V].
+    Pb(I0)
+        Return power transmitted to the beam in [W].
+    Pr(I0)
+        Return power reflected back to the generator in [W].
+    Z(f)
+        Cavity impedance in [Ohm] for a given frequency f in [Hz].
+    set_optimal_coupling(I0)
+        Set coupling to optimal value.
+    set_optimal_detune(I0)
+        Set detuning to optimal conditions.
+    set_generator(I0)
+        Set generator parameters.
+    plot_phasor(I0)
+        Plot phasor diagram.
+    is_DC_Robinson_stable(I0)
+        Check DC Robinson stability.
+    plot_DC_Robinson_stability()
+        Plot DC Robinson stability limit.
+    init_tracking(beam)
+        Initialization of the tracking.
+    track(beam)
+        Tracking method.
+    phasor_decay(time)
+        Compute the beam phasor decay during a given time span.
+    phasor_evol(profile, bin_length, charge_per_mp)
+        Compute the beam phasor evolution during the crossing of a bunch.
+    VRF(z, I0)
+        Return the total RF voltage.
+    dVRF(z, I0)
+        Return derivative of total RF voltage.
+    ddVRF(z, I0)
+        Return the second derivative of total RF voltage.
+    deltaVRF(z, I0)
+        Return the generator voltage minus beam loading voltage.
+    update_feedback()
+        Force feedback update from current CavityResonator parameters.
+    sample_voltage()
+        Sample the voltage seen by a zero charge particle during an RF period.
+    
+    References
+    ----------
+    [1] Wilson, P. B. (1994). Fundamental-mode rf design in e+ e− storage ring 
+    factories. In Frontiers of Particle Beams: Factories with e+ e-Rings 
+    (pp. 293-311). Springer, Berlin, Heidelberg.
+    
+    [2] Naoto Yamamoto, Alexis Gamelin, and Ryutaro Nagaoka. "Investigation 
+    of Longitudinal Beam Dynamics With Harmonic Cavities by Using the Code 
+    Mbtrack." IPAC’19, Melbourne, Australia, 2019.
+    """
+    def __init__(self,
+                 ring,
+                 m,
+                 R,
+                 QL,
+                 plane = "x",
+                 Q=None,
+                 detune = 0,
+                 Ncav=1,
+                 Vc=0,
+                 theta=0,
+                 local_beta=None,
+                 n_bin=75):
+        self.ring = ring
+        self.feedback = []
+        self.m = m
+        self.Ncav = Ncav
+        if Ncav != 1:
+            self.R_per_cavity = R
+        else:
+            self.R = R
+        if Q is None:
+            self.Q = QL
+        else:
+            self.Q = Q
+        self.QL = QL
+        self.detune = detune
+        self.Vc = Vc
+        self.theta = theta
+        self.beam_phasor = np.zeros(1, dtype=complex)
+        self.beam_phasor_record = np.zeros((self.ring.h), dtype=complex)
+        self.generator_phasor_record = np.zeros((self.ring.h), dtype=complex)
+        self.tracking = False
+        self.Vg = 0
+        self.theta_g = 0
+        self.Vgr = 0
+        self.theta_gr = 0
+        self.Pg = 0
+        self.n_bin = int(n_bin)
+        if local_beta is None:
+            self.weight = 1.0
+        else:
+            self.weight = local_beta / self.ring.optics.local_beta [self.plane]
+        if plane == "x" or plane == "xp":
+            self.plane = "x"
+            self.action_plane = "xp"
+        elif plane == "y" or plane == "yp":
+            self.plane = "y"
+            self.action_plane = "xp"
+        else:
+            raise ValueError("Plane must be: x, xp, y or yp")
+
+    def init_tracking(self, beam):
+        """
+        Initialization of the tracking.
+
+        Parameters
+        ----------
+        beam : Beam object
+
+        """
+        if beam.mpi_switch:
+            self.bunch_index = beam.mpi.bunch_num  # Number of the tracked bunch in this processor
+
+        self.distance = beam.distance_between_bunches
+        self.valid_bunch_index = beam.bunch_index
+        self.tracking = True
+        self.nturn = 0
+
+    def track(self, beam):
+        """
+        Track a Beam object through the CavityResonator object.
+        
+        Can be used with or without mpi.
+        If used with mpi, beam.mpi.share_distributions must be called before.
+        
+        The beam phasor is given at t=0 (synchronous particle) of the first 
+        non empty bunch.
+
+        Parameters
+        ----------
+        beam : Beam object
+
+        """
+
+        if self.tracking is False:
+            self.init_tracking(beam)
+
+        for index, bunch in enumerate(beam):
+
+            if beam.filling_pattern[index]:
+
+                if beam.mpi_switch:
+                    # get rank of bunch n° index
+                    rank = beam.mpi.bunch_to_rank(index)
+                    # mpi -> get shared bunch profile for current bunch
+                    center = beam.mpi.tau_center[rank]
+                    profile = beam.mpi.tau_profile[rank]
+                    dipole = beam.mpi.tau_dipole[rank]
+                    bin_length = center[1] - center[0]
+                    charge_per_mp = beam.mpi.charge_per_mp_all[rank]
+                    if index == self.bunch_index:
+                        sorted_index = beam.mpi.tau_sorted_index
+                    #if self.bunch_index == 0:
+                    #    print("#",center)
+                else:
+                    # no mpi -> get bunch profile for current bunch
+                    if len(bunch) != 0:
+                        (bins, sorted_index, profile,
+                         center) = bunch.binning(n_bin=self.n_bin)
+                        bin_length = center[1] - center[0]
+                        charge_per_mp = bunch.charge_per_mp
+                        self.bunch_index = index
+                        dipole = np.zeros(self.n_bin-1, dtype=np.float64)
+                        for i in range(self.n_bin - 1):
+                            dipole[i] = bunch[self.plane][sorted_index == i].sum()
+                        dipole = np.divide(dipole, profile, out=np.zeros_like(dipole), where=profile!=0)
+                        #dipole = dipole / profile # Source of RuntimeWarning
+                        #dipole[np.isnan(dipole)] = 0
+                    else:
+                        # Update filling pattern
+                        beam.update_filling_pattern()
+                        beam.update_distance_between_bunches()
+                        # save beam phasor value
+                        self.beam_phasor_record[index] = self.beam_phasor
+                        # phasor decay to be at t=0 of the next bunch
+                        self.phasor_decay(self.ring.T1)
+                        continue
+
+                kick = bunch["tau"] * 0
+                # kick = bunch[self.action_plane] * 0
+
+                # remove part of beam phasor decay to be at the start of the binning (=bins[0])
+                self.phasor_decay(center[0] - bin_length/2)
+                """if beam.mpi_switch:
+                    if (self.bunch_index < 2 and self.nturn < 1) :
+                        print("####",self.nturn,index,self.bunch_index,(self.beam_phasor),dipole[0],center[0],flush=True)
+                else:
+                    if (self.nturn < 1) :
+                        print("####",self.nturn,index,self.bunch_index,(self.beam_phasor),dipole[0],center[0])"""
+
+                if index != self.bunch_index:
+                    self.phasor_evol(profile,dipole, bin_length, charge_per_mp)
+                else:
+                    # modify beam phasor
+                    for i, dipole0 in enumerate(dipole):
+                        mp_per_bin = profile[i]
+
+                        if mp_per_bin == 0:
+                            self.phasor_decay(bin_length)
+                            continue
+
+                        ind = (sorted_index == i)
+                        #phase = self.m * self.ring.omega1 * (
+                        #    center0 + self.ring.T1 *
+                        #    (index + self.ring.h * self.nturn))
+                        #Vgene = np.real(self.generator_phasor_record[index] *
+                        #                np.exp(1j * phase))
+                        #Vbeam = np.real(self.beam_phasor)
+                        #Vtot = Vgene + Vbeam - charge_per_mp * self.loss_factor * mp_per_bin
+                        kick[ind] = np.real(self.beam_phasor) / self.ring.E0 * self.weight
+                        #if self.nturn < 1:
+                        #    if (self.bunch_index < 2 and i < 2) or (self.bunch_index < 2 and i > self.n_bin-2) :
+                        #        print("#d#",self.nturn,index,i,kick[ind][0],(self.beam_phasor),flush=True)
+                        self.beam_phasor -= 2j * charge_per_mp * self.loss_factor * mp_per_bin * dipole0
+                        self.phasor_decay(bin_length)
+                        #if (self.bunch_index < 2  and i < 2) or (self.bunch_index < 2 and i > self.n_bin-2) :
+                        #    print("##",self.nturn,i,(self.beam_phasor),dipole0,center[i])
+
+
+                # phasor decay to be at t=0 of the current bunch (=-1*bins[-1])
+                self.phasor_decay(-1 * (center[-1] + bin_length/2))
+
+                if index == self.bunch_index:
+                    bunch[self.action_plane] += kick # apply kick
+                #if self.bunch_index == 0:
+                #      print("#",self.bunch_index,index,self.beam_phasor)
+
+            # save beam phasor value
+            self.beam_phasor_record[index] = self.beam_phasor
+            #if self.bunch_index == 0 and index == 0 :
+            #    print("##",self.bunch_index,index,self.beam_phasor)
+
+            # phasor decay to be at t=0 of the next bunch
+            self.phasor_decay(self.ring.T1)
+            #if self.nturn < 1 and self.bunch_index == 0 :
+            #if self.nturn <1 and index < 2:
+            #    print("#",self.nturn,index,self.beam_phasor,flush=True)
+
+        # apply different kind of RF feedback
+        for fb in self.feedback:
+            fb.track()
+
+        self.nturn += 1
+
+    def init_phasor_track(self, beam):
+        """
+        Initialize the beam phasor for a given beam distribution using a
+        tracking like method.
+        
+        Follow the same steps as the track method but in the "rf" reference 
+        frame and without any modifications on the beam.
+
+        Parameters
+        ----------
+        beam : Beam object
+
+        """
+        if self.tracking is False:
+            self.init_tracking(beam)
+
+        n_turn = int(self.filling_time / self.ring.T0 * 10)
+
+        for i in range(n_turn):
+            for j, bunch in enumerate(beam.not_empty):
+
+                index = self.valid_bunch_index[j]
+
+                if beam.mpi_switch:
+                    # get shared bunch profile for current bunch
+                    center = beam.mpi.tau_center[j]
+                    profile = beam.mpi.tau_profile[j]
+                    dipole = beam.mpi.tau_dipole[j]
+                    bin_length = center[1] - center[0]
+                    charge_per_mp = beam.mpi.charge_per_mp_all[j]
+                else:
+                    if i == 0:
+                        # get bunch profile for current bunch
+                        (bins, sorted_index, profile,
+                         center) = bunch.binning(n_bin=self.n_bin)
+                        dipole = np.zeros(self.n_bin-1, dtype=np.float64)
+                        for i in range(self.n_bin - 1):
+                            dipole[i] = bunch[self.plane][sorted_index == i].sum()
+                        dipole = np.divide(dipole, profile, out=np.zeros_like(dipole), where=profile!=0)
+                        if j == 0:
+                            self.profile_save = np.zeros((
+                                len(beam),
+                                len(profile),
+                            ))
+                            self.center_save = np.zeros((
+                                len(beam),
+                                len(center),
+                            ))
+                            self.dipole_save = np.zeros((
+                                len(beam),
+                                len(dipole),
+                            ))
+                        self.profile_save[j, :] = profile
+                        self.center_save[j, :] = center
+                        self.dipole_save[j, :] = dipole
+                    else:
+                        profile = self.profile_save[j, :]
+                        center = self.center_save[j, :]
+                        dipole = self.dipole_save[j, :]
+
+                    bin_length = center[1] - center[0]
+                    charge_per_mp = bunch.charge_per_mp
+
+                self.phasor_decay(center[0] - bin_length/2)
+                self.phasor_evol(profile,dipole,
+                                 bin_length,
+                                 charge_per_mp)
+                self.phasor_decay(-1 * (center[-1] + bin_length/2))
+                self.phasor_decay((self.distance[index] * self.ring.T1))
+
+    def phasor_decay(self, time,ref_frame="beam"):
+        """
+        Compute the beam phasor decay during a given time span, assuming that 
+        no particles are crossing the cavity during the time span.
+
+        Parameters
+        ----------
+        time : float
+            Time span in [s], can be positive or negative.
+        ref_frame : string, optional
+            Reference frame to be used, can be "beam" or "rf".
+
+        """
+        if ref_frame == "beam":
+            delta = self.wr
+        elif ref_frame == "rf":
+            delta = (self.wr - self.m * self.ring.omega1)
+        self.beam_phasor = self.beam_phasor * np.exp(
+            (-1 / self.filling_time + 1j*delta) * time)
+
+    def phasor_evol(self, profile, dipole, bin_length, charge_per_mp):
+        """
+        Compute the beam phasor evolution during the crossing of a bunch using 
+        an analytic formula [1].
+        
+        Assume that the phasor decay happens before the beam loading.
+
+        Parameters
+        ----------
+        profile : array
+            Longitudinal profile of the bunch in [number of macro-particle].
+        bin_length : float
+            Length of a bin in [s].
+        charge_per_mp : float
+            Charge per macro-particle in [C].
+            
+        References
+        ----------
+        [1] mbtrack2 manual.
+            
+        """
+        
+        delta = self.wr
+
+        n_bin = len(profile)
+
+        # Phasor decay during crossing time
+        deltaT = n_bin * bin_length
+        self.phasor_decay(deltaT)
+
+        # Phasor evolution due to induced voltage by marco-particles
+        k = np.arange(0, n_bin)
+        var = np.exp(
+            (-1 / self.filling_time + 1j*delta) * (n_bin-k) * bin_length)
+        sum_tot = np.sum(profile * dipole * var)
+        sum_val = -2j * sum_tot * charge_per_mp * self.loss_factor 
+        self.beam_phasor += sum_val
+
+        """
+        for i, dipole0 in enumerate(dipole):
+            mp_per_bin = profile[i]
+
+            if mp_per_bin == 0:
+                self.phasor_decay(bin_length)
+                continue
+
+            self.beam_phasor -= 2j * charge_per_mp * self.loss_factor * mp_per_bin * dipole0
+            self.phasor_decay(bin_length)
+        """
+
+    def init_phasor(self, beam):
+        """
+        Initialize the beam phasor for a given beam distribution using an
+        analytic formula [1].
+        
+        No modifications on the Beam object.
+
+        Parameters
+        ----------
+        beam : Beam object
+            
+        References
+        ----------
+        [1] mbtrack2 manual.
+
+        """
+
+        # Initialization
+        if self.tracking is False:
+            self.init_tracking(beam)
+
+        N = self.n_bin - 1
+        delta = (self.wr - self.m * self.ring.omega1)
+        n_turn = int(self.filling_time / self.ring.T0 * 10)
+
+        T = np.ones(self.ring.h) * self.ring.T1
+        bin_length = np.zeros(self.ring.h)
+        charge_per_mp = np.zeros(self.ring.h)
+        profile = np.zeros((N, self.ring.h))
+        center = np.zeros((N, self.ring.h))
+        dipole = np.zeros((N, self.ring.h))
+
+        # Gather beam distribution data
+        for j, bunch in enumerate(beam.not_empty):
+            index = self.valid_bunch_index[j]
+            if beam.mpi_switch:
+                beam.mpi.share_distributions(beam, n_bin=self.n_bin)
+                center[:, index] = beam.mpi.tau_center[j]
+                profile[:, index] = beam.mpi.tau_profile[j]
+                dipole = beam.mpi.tau_dipole[j]
+                bin_length[index] = center[1, index] - center[0, index]
+                charge_per_mp[index] = beam.mpi.charge_per_mp_all[j]
+            else:
+                (bins, sorted_index, profile[:, index],
+                 center[:, index]) = bunch.binning(n_bin=self.n_bin)
+                dipole = np.zeros(self.n_bin-1, dtype=np.float64)
+                for i in range(self.n_bin - 1):
+                    dipole[i] = bunch[self.plane][sorted_index == i].sum()
+                dipole = dipole / profile # Source of RuntimeWarning
+                dipole[np.isnan(dipole)] = 0
+                bin_length[index] = center[1, index] - center[0, index]
+                charge_per_mp[index] = bunch.charge_per_mp
+            T[index] -= (center[-1, index] + bin_length[index] / 2)
+            if index != 0:
+                T[index - 1] += (center[0, index] - bin_length[index] / 2)
+        T[self.ring.h - 1] += (center[0, 0] - bin_length[0] / 2)
+
+        # Compute matrix coefficients
+        k = np.arange(0, N)
+        Tkj = np.zeros((N, self.ring.h))
+        for j in range(self.ring.h):
+            sum_t = np.array(
+                [T[n] + N * bin_length[n] for n in range(j + 1, self.ring.h)])
+            Tkj[:, j] = (N-k) * bin_length[j] + T[j] + np.sum(sum_t)
+
+        var = np.exp((-1 / self.filling_time + 1j*delta) * Tkj)
+        sum_tot = np.sum((profile*charge_per_mp*dipole) * var)
+
+        # Use the formula n_turn times
+        for i in range(n_turn):
+            # Phasor decay during one turn
+            self.phasor_decay(self.ring.T0, ref_frame="rf")
+            # Phasor evolution due to induced voltage by marco-particles during one turn
+            sum_val = 2 * sum_tot * self.loss_factor
+            self.beam_phasor -= sum_val
+
+        # Replace phasor at t=0 (synchronous particle) of the first non empty bunch.
+        idx0 = self.valid_bunch_index[0]
+        self.phasor_decay(center[-1, idx0] + bin_length[idx0] / 2,
+                          ref_frame="rf")
+
+    @property
+    def generator_phasor(self):
+        """Generator phasor in [V]"""
+        return self.Vg * np.exp(1j * self.theta_g)
+
+    @property
+    def cavity_phasor(self):
+        """Cavity total phasor in [V]"""
+        return self.generator_phasor + self.beam_phasor
+
+    @property
+    def cavity_phasor_record(self):
+        """Last cavity phasor value of each bunch in [V]"""
+        return self.generator_phasor_record + self.beam_phasor_record
+
+    @property
+    def ig_phasor_record(self):
+        """Last current generator phasor of each bunch in [A]"""
+        for FB in self.feedback:
+            if isinstance(FB, (DipoleProportionalIntegralLoop, DipoleDirectFeedback)):
+                return FB.ig_phasor_record
+        return np.zeros(self.ring.h)
+    
+    @property
+    def DFB_ig_phasor(self):
+        """Last current generator phasor of each bunch in [A]"""
+        for FB in self.feedback:
+            if isinstance(FB, DipoleDirectFeedback):
+                return FB.DFB_ig_phasor
+        return np.zeros(self.ring.h)
+
+    @property
+    def cavity_voltage(self):
+        """Cavity total voltage in [V]"""
+        return np.abs(self.cavity_phasor)
+
+    @property
+    def cavity_phase(self):
+        """Cavity total phase in [rad]"""
+        return np.angle(self.cavity_phasor)
+
+    @property
+    def beam_voltage(self):
+        """Beam loading voltage in [V]"""
+        return np.abs(self.beam_phasor)
+
+    @property
+    def beam_phase(self):
+        """Beam loading phase in [rad]"""
+        return np.angle(self.beam_phasor)
+
+    @property
+    def loss_factor(self):
+        """Cavity loss factor in [V/C]"""
+        return  self.wr * self.R / (2 * self.Q) 
+
+    @property
+    def m(self):
+        """Harmonic number of the cavity"""
+        return self._m
+
+    @m.setter
+    def m(self, value):
+        self._m = value
+
+    @property
+    def Ncav(self):
+        """Number of cavities"""
+        return self._Ncav
+
+    @Ncav.setter
+    def Ncav(self, value):
+        self._Ncav = value
+
+    @property
+    def R_per_cavity(self):
+        """Shunt impedance of a single cavity in [Ohm], defined as 
+        0.5*Vc*Vc/Pc."""
+        return self._R_per_cavity
+
+    @R_per_cavity.setter
+    def R_per_cavity(self, value):
+        self._R_per_cavity = value
+
+    @property
+    def R(self):
+        """Shunt impedance [ohm]"""
+        return self.R_per_cavity * self.Ncav
+
+    @R.setter
+    def R(self, value):
+        self.R_per_cavity = value / self.Ncav
+
+    @property
+    def RT(self):
+        """Loaded shunt impedance [ohm]"""
+        return self.R / (1 + self.beta)
+
+    @property
+    def Q(self):
+        """Quality factor"""
+        return self._Q
+
+    @Q.setter
+    def Q(self, value):
+        self._Q = value
+
+    @property
+    def QL(self):
+        """Loaded quality factor"""
+        return self._QL
+
+    @QL.setter
+    def QL(self, value):
+        self._QL = value
+        self._beta = self.Q / self.QL - 1
+        self.update_feedback()
+
+    @property
+    def beta(self):
+        """Coupling coefficient"""
+        return self._beta
+
+    @beta.setter
+    def beta(self, value):
+        self.QL = self.Q / (1+value)
+
+    @property
+    def detune(self):
+        """Cavity detuning [Hz] - defined as (fr - m*f1)"""
+        return self._detune
+
+    @detune.setter
+    def detune(self, value):
+        self._detune = value
+        self._fr = self.detune + self.m * self.ring.f1
+        self._wr = self.fr * 2 * np.pi
+        self._psi = np.arctan(self.QL * (self.fr / (self.m * self.ring.f1) -
+                                         (self.m * self.ring.f1) / self.fr))
+        self.update_feedback()
+
+    @property
+    def fr(self):
+        """Resonance frequency of the cavity in [Hz]"""
+        return self._fr
+
+    @fr.setter
+    def fr(self, value):
+        self.detune = value - self.m * self.ring.f1
+
+    @property
+    def wr(self):
+        """Angular resonance frequency in [Hz.rad]"""
+        return self._wr
+
+    @wr.setter
+    def wr(self, value):
+        self.detune = (value - self.m * self.ring.f1) * 2 * np.pi
+
+    @property
+    def psi(self):
+        """Tuning angle in [rad]"""
+        return self._psi
+
+    @psi.setter
+    def psi(self, value):
+        delta = (self.ring.f1 * self.m * np.tan(value) /
+                 self.QL)**2 + 4 * (self.ring.f1 * self.m)**2
+        fr = (self.ring.f1 * self.m * np.tan(value) / self.QL +
+              np.sqrt(delta)) / 2
+        self.detune = fr - self.m * self.ring.f1
+
+    @property
+    def filling_time(self):
+        """Cavity filling time in [s]"""
+        return 2 * self.QL / self.wr
+
+    @property
+    def Pc(self):
+        """Power dissipated in the cavity walls in [W]"""
+        return self.Vc**2 / (2 * self.R)
+
+    def Pb(self, I0):
+        """
+        Return power transmitted to the beam in [W] - near Eq. (4.2.3) in [1].
+
+        Parameters
+        ----------
+        I0 : float
+            Beam current in [A].
+
+        Returns
+        -------
+        float
+            Power transmitted to the beam in [W].
+
+        """
+        return I0 * self.Vc * np.cos(self.theta)
+
+    def Pr(self, I0):
+        """
+        Power reflected back to the generator in [W].
+
+        Parameters
+        ----------
+        I0 : float
+            Beam current in [A].
+
+        Returns
+        -------
+        float
+            Power reflected back to the generator in [W].
+
+        """
+        return self.Pg - self.Pb(I0) - self.Pc
+
+    def Vbr(self, I0):
+        """
+        Return beam voltage at resonance in [V].
+
+        Parameters
+        ----------
+        I0 : float
+            Beam current in [A].
+
+        Returns
+        -------
+        float
+            Beam voltage at resonance in [V].
+
+        """
+        return 2 * I0 * self.R / (1 + self.beta)
+
+    def Vb(self, I0):
+        """
+        Return beam voltage in [V].
+
+        Parameters
+        ----------
+        I0 : float
+            Beam current in [A].
+
+        Returns
+        -------
+        float
+            Beam voltage in [V].
+
+        """
+        return self.Vbr(I0) * np.cos(self.psi)
+
+    def Z(self, f):
+        """Cavity impedance in [Ohm] for a given frequency f in [Hz]"""
+        return self.RT * (self.fr / f) / (1 + 1j * self.QL * (self.fr / f - f / self.fr))
+
+    def set_optimal_detune(self, I0):
+        """
+        Set detuning to optimal conditions - second Eq. (4.2.1) in [1].
+
+        Parameters
+        ----------
+        I0 : float
+            Beam current in [A].
+
+        """
+        self.psi = np.arctan(-self.Vbr(I0) / self.Vc * np.sin(self.theta))
+
+    def set_optimal_coupling(self, I0):
+        """
+        Set coupling to optimal value - Eq. (4.2.3) in [1].
+
+        Parameters
+        ----------
+        I0 : float
+            Beam current in [A].
+
+        """
+        self.beta = 1 + (2 * I0 * self.R * np.cos(self.theta) / self.Vc)
+
+    def set_generator(self, I0):
+        """
+        Set generator parameters (Pg, Vgr, theta_gr, Vg and theta_g) for a 
+        given current and set of parameters.
+
+        Parameters
+        ----------
+        I0 : float
+            Beam current in [A].
+
+        """
+
+        # Generator power [W] - Eq. (4.1.2) [1] corrected with factor (1+beta)**2 instead of (1+beta**2)
+        self.Pg = self.Vc**2 * (1 + self.beta)**2 / (
+            2 * self.R * 4 * self.beta * np.cos(self.psi)**2) * (
+                (np.cos(self.theta) + 2 * I0 * self.R /
+                 (self.Vc * (1 + self.beta)) * np.cos(self.psi)**2)**2 +
+                (np.sin(self.theta) + 2 * I0 * self.R /
+                 (self.Vc *
+                  (1 + self.beta)) * np.cos(self.psi) * np.sin(self.psi))**2)
+        # Generator voltage at resonance [V] - Eq. (3.2.2) [1]
+        self.Vgr = 2 * self.beta**(1 / 2) / (1 + self.beta) * (
+            2 * self.R * self.Pg)**(1 / 2)
+        # Generator phase at resonance [rad] - from Eq. (4.1.1)
+        self.theta_gr = np.arctan(
+            (self.Vc * np.sin(self.theta) +
+             self.Vbr(I0) * np.cos(self.psi) * np.sin(self.psi)) /
+            (self.Vc * np.cos(self.theta) +
+             self.Vbr(I0) * np.cos(self.psi)**2)) - self.psi
+        # Generator voltage [V]
+        self.Vg = self.Vgr * np.cos(self.psi)
+        # Generator phase [rad]
+        self.theta_g = self.theta_gr + self.psi
+        # Set generator_phasor_record
+        self.generator_phasor_record = np.ones(
+            self.ring.h) * self.generator_phasor
+
+    def plot_phasor(self, I0):
+        """
+        Plot phasor diagram showing the vector addition of generator and beam 
+        loading voltage.
+
+        Parameters
+        ----------
+        I0 : float
+            Beam current in [A].
+            
+        Returns
+        -------
+        Figure.
+
+        """
+        def make_legend_arrow(legend, orig_handle, xdescent, ydescent, width,
+                              height, fontsize):
+            p = mpatches.FancyArrow(0,
+                                    0.5 * height,
+                                    width,
+                                    0,
+                                    length_includes_head=True,
+                                    head_width=0.75 * height)
+            return p
+
+        fig = plt.figure()
+        ax = fig.add_subplot(111, polar=True)
+        ax.set_rmax(
+            max([1.2,
+                 self.Vb(I0) / self.Vc * 1.2, self.Vg / self.Vc * 1.2]))
+        arr1 = ax.arrow(self.theta,
+                        0,
+                        0,
+                        1,
+                        alpha=0.5,
+                        width=0.015,
+                        edgecolor='black',
+                        lw=2)
+
+        arr2 = ax.arrow(self.psi + np.pi,
+                        0,
+                        0,
+                        self.Vb(I0) / self.Vc,
+                        alpha=0.5,
+                        width=0.015,
+                        edgecolor='red',
+                        lw=2)
+
+        arr3 = ax.arrow(self.theta_g,
+                        0,
+                        0,
+                        self.Vg / self.Vc,
+                        alpha=0.5,
+                        width=0.015,
+                        edgecolor='blue',
+                        lw=2)
+
+        ax.set_rticks([])  # less radial ticks
+        plt.legend([arr1, arr2, arr3], ['Vc', 'Vb', 'Vg'],
+                   handler_map={
+                       mpatches.FancyArrow:
+                       HandlerPatch(patch_func=make_legend_arrow),
+                   })
+
+        return fig
+
+    def is_CBI_stable(self,I0,tune=None,mode=[0],max_freq=5,bool_return=False):
+        """
+        Check Coupled-Bunch-Instability stability 
+        Effect of Direct RF feedback is not included.
+
+        This method caluclates the CBI growth rate from own impedance.
+
+        Parameters
+        ----------
+        I0 : float
+            Beam current in [A].
+        tune : float
+            fractional number of longitudinal tune
+        mode : float or list of float
+            Coupled Bunch Instability mode number 
+        bool_return : bool
+            if True, return bool
+            
+        Returns
+        -------
+        bool
+
+        """
+        planeIdx = 0
+        if self.plane == "y":
+            planeIdx = 1
+        if tune is None:
+            tune = self.ring.tune[planeIdx]
+        if tune > 1:
+            tune = tune - int(tune)
+        h = self.ring.h
+
+        cof = self.ring.f0*I0/2.0/self.ring.E0 * self.weight * self.ring.optics.local_beta[planeIdx]
+        if isinstance(mode,list):
+            CBImode = mode
+        else:
+            CBImode = [mode]
+
+        gr = np.zeros(len(CBImode))
+        gr_bool = np.zeros(len(CBImode),dtype=bool)
+        count = 0
+        for i in CBImode:
+            #fp = np.array([0,1,2,3,4])*self.ring.f1+i*self.ring.f0*(i+tune)
+            fp = self.ring.f0*(np.arange(max_freq)*h+(i+tune))
+            #fm = np.array([1,2,3,4,5])*self.ring.f1-i*self.ring.f0*(i+tune)
+            fm = self.ring.f0*(np.arange(1,max_freq+1)*h-(i+tune))
+            sumZ = np.sum(np.real(self.Z(fm))) - np.sum(np.real(self.Z(fp)))
+            
+            gr[count] = cof*sumZ
+            gr_bool[count] = ( gr[count] > 1/self.ring.tau[planeIdx] )
+            count +=1
+
+        if bool_return:
+            return gr_bool
+        else:
+            return gr 
+
+
+    def VRF(self, z, I0, F=1, PHI=0):
+        """Total RF voltage taking into account form factor amplitude F and form factor phase PHI"""
+        return self.Vg * np.cos(self.ring.k1 * self.m * z +
+                                self.theta_g) - self.Vb(I0) * F * np.cos(
+                                    self.ring.k1 * self.m * z + self.psi - PHI)
+
+    def dVRF(self, z, I0, F=1, PHI=0):
+        """Return derivative of total RF voltage taking into account form factor amplitude F and form factor phase PHI"""
+        return -1 * self.Vg * self.ring.k1 * self.m * np.sin(
+            self.ring.k1 * self.m * z +
+            self.theta_g) + self.Vb(I0) * F * self.ring.k1 * self.m * np.sin(
+                self.ring.k1 * self.m * z + self.psi - PHI)
+
+    def ddVRF(self, z, I0, F=1, PHI=0):
+        """Return the second derivative of total RF 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)
+
+    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)
+
+    def update_feedback(self):
+        """Force feedback update from current CavityResonator parameters."""
+        for FB in self.feedback:
+            if isinstance(FB, (DipoleProportionalIntegralLoop, DipoleDirectFeedback)):
+                FB.init_Ig2Vg_matrix()
+
+    def sample_voltage(self, n_points=1e4, index=0):
+        """
+        Sample the voltage seen by a zero charge particle during an RF period.
+
+        Parameters
+        ----------
+        n_points : int or float, optional
+            Number of sample points. The default is 1e4.
+        index : int, optional
+            RF bucket number to sample. Be carful if index > 0 as no new beam 
+            loading is not taken into account here.
+            The default is 0.
+
+        Returns
+        -------
+        pos : array of float
+            Array of position from -T1/2 to T1/2.
+        voltage_rec : array of float
+            Recoring of the voltage.
+
+        """
+        # Init
+        n_points = int(n_points)
+        index = 0
+        voltage_rec = np.zeros(n_points)
+        pos = np.linspace(-self.ring.T1 / 2, self.ring.T1 / 2, n_points)
+        DeltaT = self.ring.T1 / (n_points-1)
+
+        # From t=0 of first non empty bunch to -T1/2
+        self.phasor_decay(-self.ring.T1 / 2 + index * self.ring.T1,
+                          ref_frame="beam")
+
+        # Goes from (-T1/2) to (T1/2 + DeltaT) in n_points steps
+        for i in range(n_points):
+            phase = self.m * self.ring.omega1 * (
+                pos[i] + self.ring.T1 * (index + self.ring.h * self.nturn))
+            Vgene = np.real(self.generator_phasor_record[index] *
+                            np.exp(1j * phase))
+            Vbeam = np.real(self.beam_phasor)
+            Vtot = Vgene + Vbeam
+            voltage_rec[i] = Vtot
+            self.phasor_decay(DeltaT, ref_frame="beam")
+
+        # Get back to t=0
+        self.phasor_decay(-DeltaT * n_points + self.ring.T1 / 2 -
+                          index * self.ring.T1,
+                          ref_frame="beam")
+
+        return pos, voltage_rec
+
+
+class DipoleProportionalLoop():
+    """
+    Proportional feedback loop to control a CavityResonator amplitude and phase.
+    
+    Feedback setpoints are cav_res.Vc and cav_res.theta.
+    
+    The loop must be added to the CavityResonator object using:
+        cav_res.feedback.append(loop)
+
+    Parameters
+    ----------
+    ring : Synchrotron object
+    cav_res : 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.
+        Must be supperior or equal to 1.
+
+    """
+    def __init__(self, ring, cav_res, gain_A, gain_P, delay):
+        self.ring = ring
+        self.cav_res = cav_res
+        self.gain_A = gain_A
+        self.gain_P = gain_P
+        self.delay = int(delay)
+        if delay < 1:
+            raise ValueError("delay must be >= 1.")
+        self.volt_delay = np.ones(self.delay) * self.cav_res.Vc
+        self.phase_delay = np.ones(self.delay) * self.cav_res.theta
+
+    def track(self):
+        """
+        Tracking method for the amplitude and phase loop.
+
+        Returns
+        -------
+        None.
+
+        """
+        diff_A = self.volt_delay[-1] - self.cav_res.Vc
+        diff_P = self.phase_delay[-1] - self.cav_res.theta
+        self.cav_res.Vg -= self.gain_A * diff_A
+        self.cav_res.theta_g -= self.gain_P * diff_P
+        self.cav_res.generator_phasor_record = np.ones(
+            self.ring.h) * self.cav_res.generator_phasor
+        self.volt_delay = np.roll(self.volt_delay, 1)
+        self.phase_delay = np.roll(self.phase_delay, 1)
+        self.volt_delay[0] = self.cav_res.cavity_voltage
+        self.phase_delay[0] = self.cav_res.cavity_phase
+
+
+class DipoleTunerLoop():
+    """
+    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:
+        cav_res.feedback.append(loop)
+
+    Parameters
+    ----------
+    ring : Synchrotron object
+    cav_res : 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, cav_res, gain=0.01, avering_period=0, offset=0):
+        self.ring = ring
+        self.cav_res = cav_res
+        if avering_period == 0:
+            fs = self.ring.synchrotron_tune(
+                self.cav_res.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):
+        """
+        Tracking method for the tuner loop.
+    
+        Returns
+        -------
+        None.
+    
+        """
+        if self.count == self.avering_period:
+            diff = self.diff / self.avering_period - self.offset
+            self.cav_res.psi -= diff * self.Pgain
+            self.count = 0
+            self.diff = 0
+        else:
+            self.diff += self.cav_res.cavity_phase - self.cav_res.theta_g + self.cav_res.psi
+            self.count += 1
+
+
+class DipoleProportionalIntegralLoop():
+    """
+    Proportional Integral (PI) loop to control a CavityResonator amplitude and 
+    phase via generator current (ig) [1,2].
+    
+    Feedback reference targets (setpoints) are cav_res.Vc and cav_res.theta.
+    
+    The ProportionalIntegralLoop should be initialized only after generator 
+    parameters are set.
+    
+    The loop must be added to the CavityResonator object using:
+        cav_res.feedback.append(loop)
+        
+    When the reference target is changed, it might be needed to re-initialize 
+    the feedforward constant by calling init_FFconst().
+
+    Parameters
+    ----------
+    ring : Synchrotron object
+    cav_res : CavityResonator object
+        CavityResonator in which the loop will be added. 
+    gain : float list
+        Proportional gain (Pgain) and integral gain (Igain) of the feedback 
+        system in the form of a list [Pgain, Igain].
+        In case of normal conducting cavity (QL~1e4), the Pgain of ~1.0 and 
+        Igain of ~1e4(5) are usually used.
+        In case of super conducting cavity (QL > 1e6), the Pgain of ~100 
+        can be used.
+        In a "bad" parameter set, unstable oscillation of the cavity voltage 
+        can be caused. So, a parameter scan of the gain should be made.
+        Igain * ring.T1 / dtau is Ki defined as a the coefficients for 
+        integral part in of [1], where dtau is a clock period of the PI controller.
+    sample_num : int
+        Number of bunch over which the mean cavity voltage is computed.
+        Units are in bucket numbers.
+    every : int
+        Sampling and Clock period of the feedback controller
+        Time interval between two cavity voltage monitoring and feedback.
+        Units are in bucket numbers.
+    delay : int
+        Loop delay of the PI feedback system. 
+        Units are in bucket numbers.
+    FF : bool, optional
+        Boolean switch to use feedforward constant.
+        True is recommended to prevent a cavity voltage drop in the beginning 
+        of the tracking.
+        In case of small Pgain (QL ~ 1e4), cavity voltage drop may cause 
+        beam loss due to static Robinson.
+        Default is True.
+        
+    Methods
+    -------
+    track()
+        Tracking method for the Cavity PI control feedback.
+    init_Ig2Vg_matrix()
+        Initialize matrix for Ig2Vg_matrix.
+    init_FFconst()
+        Initialize feedforward constant.
+    Ig2Vg_matrix()
+        Return Vg from Ig using matrix formalism.
+    Ig2Vg()
+        Go from Ig to Vg and apply values.
+    Vg2Ig(Vg)
+        Return Ig from Vg (assuming constant Vg).
+
+    Notes
+    -----
+    Assumption : delay >> every ~ sample_num
+    
+    Adjusting ig(Vg) parameter to keep the cavity voltage constant (to target values).
+    The "track" method is
+       1) Monitoring the cavity voltage phasor
+       Mean cavity voltage value between specified bunch number (sample_num) is monitored
+       with specified interval period (every).
+       2) Changing the ig phasor
+       The ig phasor is changed according the difference of the specified
+       reference values with specified gain (gain).
+       By using ig instead of Vg, the cavity response can be taken account.
+       3) ig changes are reflected to Vg after the specifed delay (delay) of the system
+
+    Vc-->(rot)-->(-)-->(V->I,fac)-->PI-->Ig --> Vg
+                  |
+                 Ref
+
+    Examples
+    --------
+    PF; E0=2.5GeV, C=187m, frf=500MHz
+        QL=11800, fs0=23kHz
+        ==> gain=[0.5,1e4], sample_num=8, every=7(13ns), delay=500(1us)
+                     is one reasonable parameter set.
+        The practical clock period is 13ns.
+            ==> Igain_PF = Igain_mbtrack * Trf / 13ns = Igain_mbtrack * 0.153
+     
+    References
+    ----------
+    [1] : https://en.wikipedia.org/wiki/PID_controller
+    [2] : Yamamoto, N., Takahashi, T., & Sakanaka, S. (2018). Reduction and 
+    compensation of the transient beam loading effect in a double rf system 
+    of synchrotron light sources. PRAB, 21(1), 012001.
+    
+    """
+    def __init__(self, ring, cav_res, gain, sample_num, every, delay, IIR_cutoff=0,FF=True):
+        self.ring = ring
+        self.cav_res = cav_res
+        self.Ig2Vg_mat = np.zeros((self.ring.h, self.ring.h), dtype=complex)
+        self.ig_modulation_signal = np.zeros(self.ring.h, dtype=complex)
+        self.gain = gain
+        self.FF = FF
+
+        if delay > 0:
+            self.delay = int(delay)
+        else:
+            self.delay = 1
+        if every > 0:
+            self.every = int(every)
+        else:
+            self.every = 1
+        record_size = int(np.ceil(self.delay / self.every))
+        if record_size < 1:
+            raise ValueError("Bad parameter set : delay or every")
+        self.sample_num = int(sample_num)
+
+        # init lists for FB process
+        self.ig_phasor = np.ones(self.ring.h, dtype=complex) * self.Vg2Ig(
+            self.cav_res.generator_phasor)
+        self.ig_phasor_record = self.ig_phasor
+        self.vc_previous = np.ones(
+            self.sample_num) * self.cav_res.cavity_phasor
+        self.diff_record = np.zeros(record_size, dtype=complex)
+        self.I_record = 0 + 0j
+
+        self.sample_list = range(0, self.ring.h, self.every)
+
+        # IIR filter
+        ## IIR_cutoff ; cutoff frequecny in Hz. If 0, cutoff frequency is infinity.
+        self.IIR_init(IIR_cutoff)
+        self.init_FFconst()
+
+        # Pre caclulation for Ig2Vg
+        self.init_Ig2Vg_matrix()
+
+    def track(self, apply_changes=True):
+        """
+        Tracking method for the Cavity PI control feedback.
+
+        Returns
+        -------
+        None.
+
+        """
+        vc_list = np.concatenate([
+            self.vc_previous, self.cav_res.cavity_phasor_record
+        ])  #This line is slowing down the process.
+        self.ig_phasor.fill(self.ig_phasor[-1])
+
+        for index in self.sample_list:
+            # 2) updating Ig using last item of the list
+            diff = self.diff_record[-1] - self.FFconst
+            self.I_record += diff / self.ring.f1
+            fb_value = self.gain[0] * diff + self.gain[1] * self.I_record
+            self.ig_phasor[index:] = self.Vg2Ig(fb_value) + self.FFconst
+            # Shift the record
+            self.diff_record = np.roll(self.diff_record, 1)
+            # 1) recording diff as a first item of the list
+            mean_vc = np.mean(vc_list[index:self.sample_num + index]) * np.exp(
+                -1j * self.cav_res.theta)
+            self.diff_record[0] = self.cav_res.Vc - self.IIR(mean_vc)
+        # update sample_list for next turn
+        self.sample_list = range(index + self.every - self.ring.h, self.ring.h,
+                                 self.every)
+        # update vc_previous for next turn
+        self.vc_previous = self.cav_res.cavity_phasor_record[-self.sample_num:]
+
+        self.ig_phasor = self.ig_phasor + self.ig_modulation_signal
+        self.ig_phasor_record = self.ig_phasor
+
+        if apply_changes:
+            self.Ig2Vg()
+
+    def init_Ig2Vg_matrix(self):
+        """
+        Initialize matrix for Ig2Vg_matrix.
+        
+        Shoud be called before first use of Ig2Vg_matrix and after each cavity 
+        parameter change.
+        """
+        k = np.arange(0, self.ring.h)
+        self.Ig2Vg_vec = np.exp(-1 / self.cav_res.filling_time *
+                                (1 - 1j * np.tan(self.cav_res.psi)) *
+                                self.ring.T1 * (k+1))
+        tempV = np.exp(-1 / self.cav_res.filling_time * self.ring.T1 * k *
+                       (1 - 1j * np.tan(self.cav_res.psi)))
+        for idx in np.arange(self.ring.h):
+            self.Ig2Vg_mat[idx:, idx] = tempV[:self.ring.h - idx]
+
+    def init_FFconst(self):
+        """Initialize feedforward constant."""
+        if self.FF:
+            self.FFconst = np.mean(self.ig_phasor)
+        else:
+            self.FFconst = 0
+
+    def Ig2Vg_matrix(self):
+        """
+        Return Vg from Ig using matrix formalism.
+        Warning: self.init_Ig2Vg should be called after each CavityResonator 
+        parameter change.
+        """
+        generator_phasor_record = (
+            self.Ig2Vg_vec * self.cav_res.generator_phasor_record[-1] +
+            self.Ig2Vg_mat.dot(self.ig_phasor_record) *
+            self.cav_res.loss_factor * self.ring.T1)
+        return generator_phasor_record
+
+    def Ig2Vg(self):
+        """
+        Go from Ig to Vg.
+        
+        Apply new values to cav_res.generator_phasor_record, cav_res.Vg and
+        cav_res.theta_g from ig_phasor_record.
+        """
+        self.cav_res.generator_phasor_record = self.Ig2Vg_matrix()
+        self.cav_res.Vg = np.mean(np.abs(self.cav_res.generator_phasor_record))
+        self.cav_res.theta_g = np.mean(
+            np.angle(self.cav_res.generator_phasor_record))
+
+    def Vg2Ig(self, Vg):
+        """
+        Return Ig from Vg (assuming constant Vg).
+
+        Eq.25 of ref [2] assuming the dVg/dt = 0.
+        """
+        return Vg * (1 - 1j * np.tan(self.cav_res.psi)) / self.cav_res.RL
+
+    def IIR_init(self,cutoff):
+        """
+        f = 1/2/pi/T arccos((2-2r-r*r)/2(1-r))
+            T: Sample time
+            r: IIRcoef
+            f: cutoff freqeuncy
+        """
+        if cutoff == 0:
+            self.IIRcoef = 1.0
+        else:
+            omega = 2.0 * np.pi * cutoff
+            T = self.ring.T1 * self.every
+            alpha = np.cos(omega*T)-1
+            tmp = alpha * alpha - 2 * alpha
+            if tmp > 0:
+                self.IIRcoef = alpha + np.sqrt(tmp)
+            else:
+                self.IIRcoef = T * cutoff * 2 * np.pi
+        self.IIRout = self.cav_res.Vc
+
+    def IIR(self,input):
+        self.IIRout = (1-self.IIRcoef)*self.IIRout+self.IIRcoef*input
+        return self.IIRout
+    
+    @property
+    def IIRcutoff(self):
+        """Return IIR cutoff frequency."""
+        T = self.ring.T1 * self.every
+        return 1.0/2.0/np.pi/T * np.arccos((2-2*self.IIRcoef-self.IIRcoef*self.IIRcoef)/2/(1-self.IIRcoef))
+
+class DipoleDirectFeedback(DipoleProportionalIntegralLoop):
+    """
+    Direct RF FB (DFB) via generator current (ig) using PI controller [1,2].
+    
+    The DirectFeedback inherits from ProportionalIntegralLoop so all 
+    mandatory parameters from ProportionalIntegralLoop should be passed as
+    kwargs when initializing a DirectFeedback object.
+        
+    To avoid cavity-beam unmatching (large synchrotron oscilation of beam),
+    the CavityResonator generator parameters should be set before 
+    initialization.
+    
+    The loop must be added to the CavityResonator object using:
+        cav_res.feedback.append(loop)
+    
+    Parameters
+    ----------
+    DFB_gain : float
+        Gain of the DFB.
+    DFB_phase_shift : float
+        Phase shift of the DFB.
+    DFB_sample_num : int, optional
+        Sample number to monitor Vc for the DFB.
+        The averaged Vc value in DFB_sample_num is monitored.
+        Units are in bucket numbers.
+        If None, value from the ProportionalIntegralLoop is used.
+        Default is None.
+    DFB_every : int, optional
+        Interval to monitor and change Vc for the DFB.
+        Units are in bucket numbers.
+        If None, value from the ProportionalIntegralLoop is used.
+        Default is None.
+    DFB_delay : int, optional
+        Loop delay of the DFB.
+        Units are in bucket numbers.
+        If None, value from the ProportionalIntegralLoop is used.
+        Default is None.
+        
+    Attributes
+    ----------
+    phase_shift : float
+        Phase shift applied to DFB, defined as psi - DFB_phase_shift.
+    DFB_psi: float
+        Return detuning angle value with Direct RF feedback in [rad].
+    DFB_alpha : float
+        Return the amplitude ratio alpha of the DFB.
+    DFB_gamma : float
+        Return the gain gamma of the DFB.
+    DFB_Rs : float
+        Return the shunt impedance of the DFB in [ohm].
+    
+    Methods
+    -------
+    DFB_parameter_set(DFB_gain, DFB_phase_shift)
+        Set DFB gain and phase shift parameters.
+    track()
+        Tracking method for the Direct RF feedback.
+    DFB_Vg()
+        Return the generator voltage main and DFB components in [V].
+    DFB_fs()
+        Return the modified synchrotron frequency in [Hz].
+        
+    References
+    ----------
+    [1] : Akai, K. (2022). Stability analysis of rf accelerating mode with 
+    feedback loops under heavy beam loading in SuperKEKB. PRAB, 25(10), 
+    102002.
+    [2] : N. Yamamoto et al. (2023). Stability survey of a double RF system 
+    with RF feedback loops for bunch lengthening in a low-emittance synchrotron 
+    ring. In Proc. IPAC'23. doi:10.18429/JACoW-IPAC2023-WEPL161
+
+    """
+    def __init__(self,
+                 DFB_gain,
+                 DFB_phase_shift,
+                 DFB_sample_num=None,
+                 DFB_every=None,
+                 DFB_delay=None,
+                 **kwargs):
+        super(DipoleDirectFeedback, self).__init__(**kwargs)
+
+        if DFB_delay is not None:
+            self.DFB_delay = int(DFB_delay)
+        else:
+            self.DFB_delay = self.delay
+
+        if DFB_sample_num is not None:
+            self.DFB_sample_num = int(DFB_sample_num)
+        else:
+            self.DFB_sample_num = self.sample_num
+
+        if DFB_every is not None:
+            self.DFB_every = int(DFB_every)
+        else:
+            self.DFB_every = self.every
+
+        record_size = int(np.ceil(self.DFB_delay / self.DFB_every))
+        if record_size < 1:
+            raise ValueError("Bad parameter set : DFB_delay or DFB_every")
+
+        self.DFB_parameter_set(DFB_gain, DFB_phase_shift)
+        if np.sum(np.abs(self.cav_res.beam_phasor)) == 0:
+            cavity_phasor = self.cav_res.Vc * np.exp(1j * self.cav_res.theta)
+        else:
+            cavity_phasor = np.mean(self.cav_res.cavity_phasor_record)
+        self.DFB_VcRecord = np.ones(record_size, dtype=complex) * cavity_phasor
+        self.DFB_vc_previous = np.ones(self.DFB_sample_num,
+                                       dtype=complex) * cavity_phasor
+
+        self.DFB_sample_list = range(0, self.ring.h, self.DFB_every)
+
+    @property
+    def DFB_phase_shift(self):
+        """Return DFB phase shift."""
+        return self._DFB_phase_shift
+
+    @DFB_phase_shift.setter
+    def DFB_phase_shift(self, value):
+        """Set DFB_phase_shift and phase_shift"""
+        self._DFB_phase_shift = value
+        self._phase_shift = self.cav_res.psi - value
+
+    @property
+    def phase_shift(self):
+        """
+        Return phase shift applied to DFB.
+        Defined as self.cav_res.psi - self.DFB_phase_shift.
+        """
+        return self._phase_shift
+
+    @property
+    def DFB_psi(self):
+        """
+        Return detuning angle value with Direct RF feedback in [rad].
+    
+        Fig.4 of ref [1].
+        """
+        return (np.angle(np.mean(self.cav_res.cavity_phasor_record)) -
+                np.angle(np.mean(self.ig_phasor_record)))
+
+    @property
+    def DFB_alpha(self):
+        """
+        Return the amplitude ratio alpha of the DFB.
+        
+        Near Eq. (13) of [1].
+        """
+        fac = np.abs(
+            np.mean(self.DFB_ig_phasor) / np.mean(self.ig_phasor_record))
+        return 20 * np.log10(fac)
+
+    @property
+    def DFB_gamma(self):
+        """
+        Return the gain gamma of the DFB.
+        
+        Near Eq. (13) of [1].
+        """
+        fac = np.abs(
+            np.mean(self.DFB_ig_phasor) / np.mean(self.ig_phasor_record))
+        return fac / (1-fac)
+
+    @property
+    def DFB_Rs(self):
+        """
+        Return the shunt impedance of the DFB in [ohm].
+        
+        Eq. (15) of [1].
+        """
+        return self.cav_res.Rs / (1 + self.DFB_gamma * np.cos(self.DFB_psi))
+
+    def DFB_parameter_set(self, DFB_gain, DFB_phase_shift):
+        """
+        Set DFB gain and phase shift parameters.
+
+        Parameters
+        ----------
+        DFB_gain : float
+            Gain of the DFB.
+        DFB_phase_shift : float
+            Phase shift of the DFB.
+
+        """
+        self.DFB_gain = DFB_gain
+        self.DFB_phase_shift = DFB_phase_shift
+
+        if np.sum(np.abs(self.cav_res.beam_phasor)) == 0:
+            vc = np.ones(self.ring.h) * self.cav_res.Vc * np.exp(
+                1j * self.cav_res.theta)
+        else:
+            vc = self.cav_res.cavity_phasor_record
+        vg_drf = self.DFB_gain * vc * np.exp(1j * self.phase_shift)
+        self.DFB_ig_phasor = self.Vg2Ig(vg_drf)
+
+        self.ig_phasor = self.ig_phasor_record - self.DFB_ig_phasor
+        self.init_FFconst()
+
+    def track(self):
+        """
+        Tracking method for the Direct RF feedback.
+
+        Returns
+        -------
+        None.
+
+        """
+        super(DipoleDirectFeedback, self).track(False)
+
+        vc_list = np.concatenate(
+            [self.DFB_vc_previous, self.cav_res.cavity_phasor_record])
+        self.DFB_ig_phasor = np.roll(self.DFB_ig_phasor, 1)
+        for index in self.DFB_sample_list:
+            # 2) updating Ig using last item of the list
+            vg_drf = self.DFB_gain * self.DFB_VcRecord[-1] * np.exp(
+                1j * self.phase_shift)
+            self.DFB_ig_phasor[index:] = self.Vg2Ig(vg_drf)
+            # Shift the record
+            self.DFB_VcRecord = np.roll(self.DFB_VcRecord, 1)
+            # 1) recording Vc
+            mean_vc = np.mean(vc_list[index:self.DFB_sample_num + index])
+            self.DFB_VcRecord[0] = mean_vc
+        # update sample_list for next turn
+        self.DFB_sample_list = range(index + self.DFB_every - self.ring.h,
+                                     self.ring.h, self.DFB_every)
+        # update vc_previous for next turn
+        self.DFB_vc_previous = self.cav_res.cavity_phasor_record[
+            -self.DFB_sample_num:]
+
+        self.ig_phasor_record = self.ig_phasor + self.DFB_ig_phasor
+
+        self.Ig2Vg()
+
+    def DFB_Vg(self, vc=-1):
+        """Return the generator voltage main and DFB components in [V]."""
+        if vc == -1:
+            vc = np.mean(self.cav_res.cavity_phasor_record)
+        vg_drf = self.DFB_gain * vc * np.exp(1j * self.phase_shift)
+        vg_main = np.mean(self.cav_res.generator_phasor_record) - vg_drf
+        return vg_main, vg_drf
+
+    def DFB_fs(self, vg_main=-1, vg_drf=-1):
+        """Return the modified synchrotron frequency in [Hz]."""
+        vc = np.mean(self.cav_res.cavity_phasor_record)
+        if vg_drf == -1:
+            vg_drf = self.DFB_gain * vc * np.exp(1j * self.phase_shift)
+        if vg_main == -1:
+            vg_main = np.mean(self.cav_res.generator_phasor_record) - vg_drf
+        vg_sum = np.abs(vg_main) * np.sin(
+            np.angle(vg_main)) + np.abs(vg_drf) * np.sin(np.angle(vg_drf))
+        omega_s = 0
+        if (vg_sum) > 0.0:
+            omega_s = np.sqrt(self.ring.ac * self.ring.omega1 * (vg_sum) /
+                              self.ring.E0 / self.ring.T0)
+        return omega_s / 2 / np.pi
diff --git a/mbtrack2/utilities/beamloading.py b/mbtrack2/utilities/beamloading.py
index 1d208c2a7625349b31b2d254afbe738a0b57180e..bd0f7f9f7e68e71dde7ce165e5a630157aa9adef 100644
--- a/mbtrack2/utilities/beamloading.py
+++ b/mbtrack2/utilities/beamloading.py
@@ -295,10 +295,10 @@ class BeamLoadingEquilibrium():
                 x0 = x0 + [self.cavity_list[0].theta]
 
         if CM:
-            print("The initial center of mass offset is " +
+            print("# The initial center of mass offset is " +
                   str(self.center_of_mass() * 1e12) + " ps")
         else:
-            print("The initial energy balance is " +
+            print("# The initial energy balance is " +
                   str(self.energy_balance()) + " eV")
 
         sol = root(lambda x: self.to_solve(x, CM),
@@ -319,12 +319,12 @@ class BeamLoadingEquilibrium():
         self.PHI = sol.x[1::2]
 
         if CM:
-            print("The final center of mass offset is " +
+            print("# The final center of mass offset is " +
                   str(self.center_of_mass() * 1e12) + " ps")
         else:
-            print("The final energy balance is " + str(self.energy_balance()) +
+            print("# The final energy balance is " + str(self.energy_balance()) +
                   " eV")
-        print("The algorithm has converged: " + str(sol.success))
+        print("# The algorithm has converged: " + str(sol.success))
 
         if plot:
             self.plot_rho(self.B1 / 4, self.B2 / 4)