From aa1314e31d8c442126a94967b41aef61fb57f68f Mon Sep 17 00:00:00 2001
From: Gamelin Alexis <gamelin@synchrotron-soleil.fr>
Date: Thu, 12 Mar 2020 13:32:17 +0100
Subject: [PATCH] Restructure code and docstrings

To be PEP8 complient:
Lowercase folders
Extended docstrings
Rename and divide some files
---
 Machines/soleil.py                            |  16 +-
 Tracking/{one_turn_matrix.py => element.py}   | 170 +++++++++++-------
 Tracking/optics.py                            |  18 ++
 Tracking/parallel.py                          |  71 +++++++-
 Tracking/{beam.py => particles.py}            | 152 +++++++++++-----
 Tracking/rf.py                                |  53 ++++++
 Tracking/synchrotron.py                       |  43 +----
 .../__init__.py                               |   0
 .../instabilities.py                          |   0
 .../resistive_wall.py                         |   0
 .../tools.py                                  |   0
 .../wakefield.py                              |   0
 12 files changed, 362 insertions(+), 161 deletions(-)
 rename Tracking/{one_turn_matrix.py => element.py} (52%)
 create mode 100644 Tracking/optics.py
 rename Tracking/{beam.py => particles.py} (80%)
 create mode 100644 Tracking/rf.py
 rename {CollectiveEffect => collective_effects}/__init__.py (100%)
 rename {CollectiveEffect => collective_effects}/instabilities.py (100%)
 rename {CollectiveEffect => collective_effects}/resistive_wall.py (100%)
 rename {CollectiveEffect => collective_effects}/tools.py (100%)
 rename {CollectiveEffect => collective_effects}/wakefield.py (100%)

diff --git a/Machines/soleil.py b/Machines/soleil.py
index 4bed3a2..d2fc712 100644
--- a/Machines/soleil.py
+++ b/Machines/soleil.py
@@ -7,8 +7,9 @@ SOLEIL synchrotron parameters script.
 """
 
 import numpy as np
-from Tracking.synchrotron import Synchrotron, Electron, Optics
-from Tracking.beam import Beam
+from tracking.synchrotron import Synchrotron
+from tracking.optics import Optics
+from tracking.particles import Electron, Beam
 
 def soleil(mode = 'Uniform'):
     """
@@ -19,13 +20,13 @@ def soleil(mode = 'Uniform'):
     L = 3.540969742590899e+02
     E0 = 2.75e9
     particle = Electron()
-    ac = 4.16e-4
-    U0 = 1171e3
-    tau = np.array([0, 0, 3.27e-3])
+    ac = 1.47e-4
+    U0 = 0.310e6
+    tau = np.array([6.56e-3, 6.56e-3, 3.27e-3])
     tune = np.array([18.15687, 10.22824, 0.00502])
     emit = np.array([4.5e-9, 4.5e-9*0.01])
     sigma_0 = 8e-12
-    sigma_delta = 9e-4
+    sigma_delta = 8.6e-4
     chro = [2,3]
     
     # mean values
@@ -56,4 +57,5 @@ def soleil(mode = 'Uniform'):
         raise ValueError("{} is not a correct operation mode.".format(mode))
     
     
-    return ring
\ No newline at end of file
+    return ring
+
diff --git a/Tracking/one_turn_matrix.py b/Tracking/element.py
similarity index 52%
rename from Tracking/one_turn_matrix.py
rename to Tracking/element.py
index 30e1807..c7811a7 100644
--- a/Tracking/one_turn_matrix.py
+++ b/Tracking/element.py
@@ -1,14 +1,17 @@
 # -*- coding: utf-8 -*-
 """
-One turn map matrix elements
+This module defines the most basic elements for tracking, including Element,
+an abstract base class which is to be used as mother class to every elements
+included in the tracking.
 
 @author: gamelina
-@date: 04/03/2020
+@date: 11/03/2020
 """
 
+import numpy as np
 from abc import ABCMeta, abstractmethod
 from functools import wraps
-import numpy as np
+from tracking.particles import Beam
 
 class Element(metaclass=ABCMeta):
     """
@@ -21,101 +24,131 @@ class Element(metaclass=ABCMeta):
         """
         Track a beam object through this Element.
         This method needs to be overloaded in each Element subclass.
+        
+        Parameters
+        ----------
+        beam : Beam object
         """
         raise NotImplementedError
         
     @staticmethod
-    def all_bunches(function):
-        """
-        """
-        @wraps(function)
-        def wrapper(*args, **kwargs):
-            self = args[0]
-            beam = args[1]
-            if (beam.mpi_switch == True):
-                function(self, beam[beam.mpi.bunch_num], *args[2:], **kwargs)
-            else:
-                for bunch in beam:
-                    function(self, bunch, *args[2:], **kwargs)
-        return wrapper
-    
-    @staticmethod
-    def not_empty(function):
+    def parallel(track):
         """
+        Defines the decorator @parallel which handle the embarrassingly 
+        parallel case which happens when there is no bunch to bunch 
+        interaction in the tracking routine.
+        
+        Adding @Element.parallel allows to write the track method of the 
+        Element subclass for a Bunch object instead of a Beam object.
+        
+        Parameters
+        ----------
+        track : function, method of an Element subclass
+            track method of an Element subclass which takes a Bunch object as
+            input
+            
+        Returns
+        -------
+        track_wrapper: function, method of an Element subclass
+            track method of an Element subclass which takes a Beam object or a
+            Bunch object as input
         """
-        @wraps(function)
-        def wrapper(*args, **kwargs):
-            self = args[0]
-            beam = args[1]
-            if (beam.mpi_switch == True):
-                function(self, beam[beam.mpi.bunch_num], *args[2:], **kwargs)
+        @wraps(track)
+        def track_wrapper(*args, **kwargs):
+            if isinstance(args[1], Beam):
+                self = args[0]
+                beam = args[1]
+                if (beam.mpi_switch == True):
+                    track(self, beam[beam.mpi.bunch_num], *args[2:], **kwargs)
+                else:
+                    for bunch in beam.not_empty:
+                        track(self, bunch, *args[2:], **kwargs)
             else:
-                for bunch in beam.not_empty:
-                    function(self, bunch, *args[2:], **kwargs)
-        return wrapper
-
+                self = args[0]
+                bunch = args[1]
+                track(self, bunch, *args[2:], **kwargs)
+        return track_wrapper
         
-class Long_one_turn(Element):
+class LongitudinalMap(Element):
     """
-    Longitudinal transform for a single turn.
+    Longitudinal map for a single turn in the synchrotron.
+    
+    Parameters
+    ----------
+    ring : Synchrotron object
     """
     
     def __init__(self, ring):
         self.ring = ring
         
-    @Element.not_empty
+    @Element.parallel
     def track(self, bunch):
-        """track"""
-        bunch["delta"] -= self.ring.U0/self.ring.E0
-        bunch["tau"] -= self.ring.ac*self.ring.T0*bunch["delta"]
+        """
+        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"] -= self.ring.U0 / self.ring.E0
+        bunch["tau"] -= self.ring.ac * self.ring.T0 * bunch["delta"]
         
 class SynchrotronRadiation(Element):
-    """SyncRad"""
+    """
+    Element to handle synchrotron radiation, radiation damping and quantum 
+    excitation, for a single turn in the synchrotron.
+    
+    Parameters
+    ----------
+    ring : Synchrotron object
+    switch : bool array of shape (3,), optional
+        allow to choose on which plane the synchrotron radiation is active
+    """
     
     def __init__(self, ring, switch = np.ones((3,), dtype=bool)):
         self.ring = ring
         self.switch = switch
         
-    @Element.not_empty        
+    @Element.parallel        
     def track(self, bunch):
-        """track"""
+        """
+        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
+        """
         if (self.switch[0] == True):
             rand = np.random.normal(size=len(bunch))
             bunch["delta"] = ((1 - 2*self.ring.T0/self.ring.tau[2])*bunch["delta"] +
                  2*self.ring.sigma_delta*(self.ring.T0/self.ring.tau[2])**0.5*rand)
+            
         if (self.switch[1] == True):
             rand = np.random.normal(size=(len(bunch),2))
             bunch["x"] += self.ring.sigma[0]*(2*self.ring.T0/self.ring.tau[0])**0.5*rand[:,0]
             bunch["xp"] = (1 + bunch["delta"])/(1 + bunch["delta"] + bunch.energy_change)*bunch["xp"]
             bunch["xp"] += self.ring.sigma[1]*(2*self.ring.T0/self.ring.tau[0])**0.5*rand[:,1]
+        
         if (self.switch[2] == True):
             rand = np.random.normal(size=(len(bunch),2))
             bunch["y"] += self.ring.sigma[2]*(2*self.ring.T0/self.ring.tau[1])**0.5*rand[:,0]
             bunch["yp"] = (1 + bunch["delta"])/(1 + bunch["delta"] + bunch.energy_change)*bunch["yp"]
             bunch["yp"] += self.ring.sigma[3]*(2*self.ring.T0/self.ring.tau[1])**0.5*rand[:,1]
-        bunch.energy_change = 0
-
-class RF_cavity(Element):
-    """ Perfect RF cavity class for main and harmonic RF cavities."""
-    def __init__(self, ring, m, Vc, theta):
-        self.ring = ring
-        self.m = m # Harmonic number of the cavity
-        self.Vc = Vc # Amplitude of Cavity voltage [V]
-        self.theta = theta # phase of Cavity voltage
-        
-    @Element.not_empty    
-    def track(self,bunch):
-        """Track """
-        energy_change = self.Vc/self.ring.E0*np.cos(self.m*self.ring.omega1*bunch["tau"] + self.theta)
-        bunch["delta"] += energy_change
-        bunch.energy_change += energy_change
         
-    def value(self, val):
-        return self.Vc/self.ring.E0*np.cos(self.m*self.ring.omega1*val + self.theta)
+        # Reset energy change to 0 for next turn
+        bunch.energy_change = 0
         
-class Trans_one_turn(Element):
+class TransverseMap(Element):
     """
-    Transverse transformation for a single turn.
+    Transverse map for a single turn in the synchrotron.
+    
+    Parameters
+    ----------
+    ring : Synchrotron object
     """
     
     def __init__(self, ring):
@@ -127,13 +160,26 @@ class Trans_one_turn(Element):
         self.dispp = self.ring.mean_optics.dispp
         self.phase_advance = self.ring.tune[0:2]*2*np.pi
     
-    @Element.not_empty    
+    @Element.parallel    
     def track(self, bunch):
-        """track"""
+        """
+        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
+        """
 
+        # Compute phase adcence which depends on energy via chromaticity
         phase_advance_x = self.phase_advance[0]*(1+self.ring.chro[0]*bunch["delta"])
         phase_advance_y = self.phase_advance[1]*(1+self.ring.chro[1]*bunch["delta"])
+        
+        # 6x6 matrix corresponding to (x, xp, delta, y, yp, delta)
         matrix = np.zeros((6,6,len(bunch)))
+        
+        # Horizontal
         matrix[0,0,:] = np.cos(phase_advance_x) + self.alpha[0]*np.sin(phase_advance_x)
         matrix[0,1,:] = self.beta[0]*np.sin(phase_advance_x)
         matrix[0,2,:] = self.disp[0]
@@ -142,6 +188,7 @@ class Trans_one_turn(Element):
         matrix[1,2,:] = self.dispp[0]
         matrix[2,2,:] = 1
         
+        # Vertical
         matrix[3,3,:] = np.cos(phase_advance_y) + self.alpha[1]*np.sin(phase_advance_y)
         matrix[3,4,:] = self.beta[1]*np.sin(phase_advance_y)
         matrix[3,5,:] = self.disp[1]
@@ -158,5 +205,4 @@ class Trans_one_turn(Element):
         bunch["x"] = x
         bunch["xp"] = xp
         bunch["y"] = y
-        bunch["yp"] = yp
-
+        bunch["yp"] = yp
\ No newline at end of file
diff --git a/Tracking/optics.py b/Tracking/optics.py
new file mode 100644
index 0000000..39b8fba
--- /dev/null
+++ b/Tracking/optics.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+"""
+Created on Wed Mar 11 18:00:28 2020
+
+@author: gamelina
+"""
+
+class Optics:
+    """Handle optic functions"""
+    def __init__(self, beta, alpha, disp, dispp):
+        self.beta = beta
+        self.alpha = alpha
+        self.disp = disp
+        self.dispp = dispp
+
+    @property
+    def gamma(self):
+        return (1 + self.alpha**2)/self.beta
\ No newline at end of file
diff --git a/Tracking/parallel.py b/Tracking/parallel.py
index ab2eb47..a3a29cd 100644
--- a/Tracking/parallel.py
+++ b/Tracking/parallel.py
@@ -10,8 +10,44 @@ Module to handle parallel computation
 import numpy as np
 
 class Mpi:
-    """ Class which handle mpi
+    """
+    Class which handle parallel computation via the mpi4py module [1].
     
+    Parameters
+    ----------
+    filling_pattern : bool array of shape (h,)
+        Filling pattern of the beam, like Beam.filling_pattern
+        
+    Attributes
+    ----------
+    comm : MPI.Intracomm object
+        MPI intra-comminicator of the processor group, used to manage 
+        communication between processors. 
+    rank : int
+        Rank of the processor which run the program
+    size : int
+        Number of processor within the processor group (in fact in the 
+        intra-comminicator group)
+    table : int array of shape (size, 2)
+        Table of correspondance between the rank of the processor and its 
+        associated bunch number
+    bunch_num : int
+        Return the bunch number corresponding to the current processor
+        
+    Methods
+    -------
+    write_table(filling_pattern)
+        Write a table with the rank and the corresponding bunch number for each
+        bunch of the filling pattern
+    rank_to_bunch(rank)
+        Return the bunch number corresponding to rank
+    bunch_to_rank(bunch_num)
+        Return the rank corresponding to the bunch number bunch_num
+        
+    References
+    ----------
+    [1] L. Dalcin, P. Kler, R. Paz, and A. Cosimo, Parallel Distributed 
+    Computing using Python, Advances in Water Resources, 34(9):1124-1139, 2011.
     """
     
     def __init__(self, filling_pattern):
@@ -29,6 +65,11 @@ class Mpi:
         """
         Write a table with the rank and the corresponding bunch number for each
         bunch of the filling pattern
+        
+        Parameters
+        ----------
+        filling_pattern : bool array of shape (h,)
+            Filling pattern of the beam, like Beam.filling_pattern
         """
         if(filling_pattern.sum() != self.size):
             raise ValueError("The number of processors must be equal to the"
@@ -39,11 +80,35 @@ class Mpi:
         self.table = table
     
     def rank_to_bunch(self, rank):
-        """Return the bunch number corresponding to rank"""
+        """
+        Return the bunch number corresponding to rank
+        
+        Parameters
+        ----------
+        rank : int
+            Rank of a processor
+            
+        Returns
+        -------
+        bunch_num : int
+            Bunch number corresponding to the input rank
+        """
         return self.table[rank,1]
     
     def bunch_to_rank(self, bunch_num):
-        """Return the rank corresponding to the bunch number bunch_num"""
+        """
+        Return the rank corresponding to the bunch number bunch_num
+        
+        Parameters
+        ----------
+        bunch_num : int
+            Bunch number
+            
+        Returns
+        -------
+        rank : int
+            Rank of the processor which tracks the input bunch number
+        """
         try:
             rank = np.where(self.table[:,1] == bunch_num)[0][0]
         except IndexError:
diff --git a/Tracking/beam.py b/Tracking/particles.py
similarity index 80%
rename from Tracking/beam.py
rename to Tracking/particles.py
index 75143ac..0839a2f 100644
--- a/Tracking/beam.py
+++ b/Tracking/particles.py
@@ -1,14 +1,45 @@
 # -*- coding: utf-8 -*-
 """
-Beam and bunch elements
+Module where particles, bunches and beams are described as objects.
 
 @author: Alexis Gamelin
-@date: 17/01/2020
+@date: 11/03/2020
 """
 
 import numpy as np
 import pandas as pd
-from Tracking.parallel import Mpi
+from tracking.parallel import Mpi
+from scipy.constants import c, m_e, m_p, e
+
+class Particle:
+    """ Define a particle object
+
+    Attributes
+    ----------
+    mass : float
+        total particle mass in [kg]
+    charge : float
+        electrical charge in [C]
+    E_rest : float
+        particle rest energy in [eV]
+    """
+    def __init__(self, mass, charge):
+        self.mass = mass
+        self.charge = charge
+
+    @property
+    def E_rest(self):
+        return self.mass * c ** 2 / e
+    
+class Electron(Particle):
+    """ Define an electron"""
+    def __init__(self):
+        super().__init__(m_e, -1*e)
+        
+class Proton(Particle):
+    """ Define a proton"""
+    def __init__(self):
+        super().__init__(m_p, e)
 
 class Bunch:
     """
@@ -36,11 +67,28 @@ class Bunch:
         Number of particles in the bunch
     current : float
         Bunch current in [A]
+    mean : array of shape (6,)
+        Mean position of alive particles for each coordinates
+    std : array of shape (6,)
+        Standard deviation of the position of alive particles for each 
+        coordinates
+    emit : array of shape (3,)
+        Bunch emittance for each plane [1]. !!! -> Correct for long ?
+    energy_change : series of shape (alive,) 
+        Store the particle relative energy change in the last turn. Only the
+        values for alive particles are returned. Used by the 
+        SynchrotronRadiation class to compute radiation damping. To be changed 
+        by Element objects which change the energy, for example RF cavities.
         
     Methods
     -------
     init_gaussian(cov=None, mean=None, **kwargs)
         Initialize bunch particles with 6D gaussian phase space.
+        
+    References
+    ----------
+    [1] Wiedemann, H. (2015). Particle accelerator physics. 4th edition. 
+    Springer, Eq.(8.39) of p224.
     """
     
     def __init__(self, ring, mp_number=1e3, current=1e-3, alive=True):
@@ -85,18 +133,6 @@ class Bunch:
     def __repr__(self):
         """Return representation of alive particles"""
         return f'{self[:]!r}'
-    
-    @property
-    def energy_change(self):
-        """Store the particle relative energy change in the last turn. Used by
-        the SynchrotronRadiation class to compute radiation damping. To be
-        changed by Element objects which change the energy, for example RF
-        cavities."""
-        return self._energy_change.loc[self.alive]
-    
-    @energy_change.setter
-    def energy_change(self, value):
-        self._energy_change.loc[self.alive] = value 
         
     @property
     def mp_number(self):
@@ -147,46 +183,24 @@ class Bunch:
     @property    
     def mean(self):
         """
-        Compute the mean position of alive particles for each 
-        coordinates.
-        
-        Returns
-        -------
-        mean : numpy array
-            mean position of alive particles
+        Return the mean position of alive particles for each coordinates.
         """
         mean = [[self[name].mean()] for name in self]
-        return np.array(mean)
+        return np.squeeze(np.array(mean))
     
     @property
     def std(self):
         """
-        Compute the standard deviation of the position of alive 
+        Return the standard deviation of the position of alive 
         particles for each coordinates.
-        
-        Returns
-        -------
-        std : numpy array
-            standard deviation of the position of alive particles
         """
         std = [[self[name].std()] for name in self]
-        return np.array(std)
+        return np.squeeze(np.array(std))
     
     @property    
     def emit(self):
         """
-        Compute the bunch emittance for each plane [1].
-        Correct definition of long emit ?
-        
-        Returns
-        -------
-        emit : numpy array
-            bunch emittance
-        
-        References
-        ----------
-        [1] Wiedemann, H. (2015). Particle accelerator physics. 4th 
-        edition. Springer, Eq.(8.39) of p224.
+        Return the bunch emittance for each plane.
         """
         emitX = (np.mean(self['x']**2)*np.mean(self['xp']**2) - 
                  np.mean(self['x']*self['xp'])**2)**(0.5)
@@ -195,12 +209,26 @@ class Bunch:
         emitS = (np.mean(self['tau']**2)*np.mean(self['delta']**2) - 
                  np.mean(self['tau']*self['delta'])**2)**(0.5)
         return np.array([emitX, emitY, emitS])
+    
+    @property
+    def energy_change(self):
+        """Store the particle relative energy change in the last turn. Used by
+        the SynchrotronRadiation class to compute radiation damping. To be
+        changed by Element objects which change the energy, for example RF
+        cavities."""
+        return self._energy_change.loc[self.alive]
+    
+    @energy_change.setter
+    def energy_change(self, value):
+        self._energy_change.loc[self.alive] = value 
         
     def init_gaussian(self, cov=None, mean=None, **kwargs):
         """
         Initialize bunch particles with 6D gaussian phase space.
         Covariance matrix is taken from [1].
         
+        !!! -> disp & dispp not included yet.
+
         Parameters
         ----------
         cov : (6,6) array, optional
@@ -259,8 +287,25 @@ class Beam:
         Total bunch charge in [C]
     particle_number : int
         Total number of particle in the beam
-    filling_pattern : array of bool
+    filling_pattern : bool array of shape (ring.h,)
         Filling pattern of the beam
+    bunch_current : array of shape (ring.h,)
+        Current in each bunch in [A]
+    bunch_charge : array of shape (ring.h,)
+        Charge in each bunch in [C]
+    bunch_particle : array of shape (ring.h,)
+        Particle number in each bunch
+    bunch_mean : array of shape (6, ring.h)
+        Mean position of alive particles for each bunch
+    bunch_std : array of shape (6, ring.h)
+        Standard deviation of the position of alive particles for each bunch        
+    bunch_emit : array of shape (6, ring.h)
+        Bunch emittance of alive particles for each bunch
+    mpi : Mpi object
+    mpi_switch : bool
+        Status of MPI parallelisation, should not be changed directly but with
+        mpi_init() and mpi_close()
+        
         
     Methods
     ------
@@ -268,6 +313,13 @@ class Beam:
         Initialize beam with a given filling pattern and marco-particle number 
         per bunch. Then initialize the different bunches with a 6D gaussian
         phase space.
+    mpi_init()
+        Switch on MPI parallelisation and initialise a Mpi object
+    mpi_gather()
+        Gather beam, all bunches of the different processors are sent to 
+        all processors. Rather slow
+    mpi_close()
+        Call mpi_gather and switch off MPI parallelisation
     """
     
     def __init__(self, ring, bunch_list=None):
@@ -408,7 +460,7 @@ class Beam:
         bunches"""
         bunch_mean = np.zeros((6,self.ring.h))
         for index, bunch in enumerate(self):
-            bunch_mean[:,index] = np.squeeze(bunch.mean)
+            bunch_mean[:,index] = bunch.mean
         return bunch_mean
     
     @property
@@ -417,7 +469,7 @@ class Beam:
         particles for each bunches"""
         bunch_std = np.zeros((6,self.ring.h))
         for index, bunch in enumerate(self):
-            bunch_std[:,index] = np.squeeze(bunch.std)
+            bunch_std[:,index] = bunch.std
         return bunch_std
     
     @property
@@ -426,11 +478,11 @@ class Beam:
         bunches and each plane"""
         bunch_emit = np.zeros((3,self.ring.h))
         for index, bunch in enumerate(self):
-            bunch_emit[:,index] = np.squeeze(bunch.emit)
+            bunch_emit[:,index] = bunch.emit
         return bunch_emit
     
     def mpi_init(self):
-        """ Initialise mpi """
+        """Switch on MPI parallelisation and initialise a Mpi object"""
         self.mpi = Mpi(self.filling_pattern)
         self.mpi_switch = True
         
@@ -445,6 +497,12 @@ class Beam:
         bunches = self.mpi.comm.allgather(bunch)
         for rank in range(self.mpi.size):
             self[self.mpi.rank_to_bunch(rank)] = bunches[rank]
+            
+    def mpi_close(self):
+        """Call mpi_gather and switch off MPI parallelisation"""
+        self.mpi_gather()
+        self.mpi_switch = False
+        self.mpi = None
         
         
         
diff --git a/Tracking/rf.py b/Tracking/rf.py
new file mode 100644
index 0000000..24928d3
--- /dev/null
+++ b/Tracking/rf.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+"""
+This module handles radio-frequency (RF) cavitiy elements. 
+
+@author: gamelina
+@date: 11/03/2020
+"""
+
+import numpy as np
+from tracking.element import Element
+
+class RFCavity(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]
+    theta : float
+        Phase of Cavity voltage
+    """
+    def __init__(self, ring, m, Vc, theta):
+        self.ring = ring
+        self.m = m 
+        self.Vc = Vc
+        self.theta = theta
+        
+    @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
+        """
+        energy_change = self.Vc / self.ring.E0 * np.cos(
+                self.m * self.ring.omega1 * bunch["tau"] + self.theta )
+        bunch["delta"] += energy_change
+        # energy_change is used by SynchrotronRadiation to compute radiation 
+        # damping
+        bunch.energy_change += energy_change
+        
+    def value(self, val):
+        return self.Vc / self.ring.E0 * np.cos( 
+                self.m * self.ring.omega1 * val + self.theta )
\ No newline at end of file
diff --git a/Tracking/synchrotron.py b/Tracking/synchrotron.py
index a75f98b..bc3d7ad 100644
--- a/Tracking/synchrotron.py
+++ b/Tracking/synchrotron.py
@@ -7,49 +7,8 @@ General elements
 """
 
 import numpy as np
-from scipy.constants import c, m_e, m_p, e
+from scipy.constants import c, e
 
-class Particle:
-    """ Define a particle
-
-    Attributes
-    ----------
-    mass : float
-        total particle mass in [kg]
-    charge : float
-        electrical charge in [C]
-    E_rest : float
-        particle rest energy in [eV]
-    """
-    def __init__(self, mass, charge):
-        self.mass = mass
-        self.charge = charge
-
-    @property
-    def E_rest(self):
-        return self.mass * c ** 2 / e
-    
-class Electron(Particle):
-    """ Define an electron"""
-    def __init__(self):
-        super().__init__(m_e, -1*e)
-        
-class Proton(Particle):
-    """ Define a proton"""
-    def __init__(self):
-        super().__init__(m_p, e)
-
-class Optics:
-    """Handle optic functions"""
-    def __init__(self, beta, alpha, disp, dispp):
-        self.beta = beta
-        self.alpha = alpha
-        self.disp = disp
-        self.dispp = dispp
-
-    @property
-    def gamma(self):
-        return (1 + self.alpha**2)/self.beta
         
 class Synchrotron:
     """ Synchrotron class to store main properties. """
diff --git a/CollectiveEffect/__init__.py b/collective_effects/__init__.py
similarity index 100%
rename from CollectiveEffect/__init__.py
rename to collective_effects/__init__.py
diff --git a/CollectiveEffect/instabilities.py b/collective_effects/instabilities.py
similarity index 100%
rename from CollectiveEffect/instabilities.py
rename to collective_effects/instabilities.py
diff --git a/CollectiveEffect/resistive_wall.py b/collective_effects/resistive_wall.py
similarity index 100%
rename from CollectiveEffect/resistive_wall.py
rename to collective_effects/resistive_wall.py
diff --git a/CollectiveEffect/tools.py b/collective_effects/tools.py
similarity index 100%
rename from CollectiveEffect/tools.py
rename to collective_effects/tools.py
diff --git a/CollectiveEffect/wakefield.py b/collective_effects/wakefield.py
similarity index 100%
rename from CollectiveEffect/wakefield.py
rename to collective_effects/wakefield.py
-- 
GitLab