Skip to content
Snippets Groups Projects
Commit ac15e7e6 authored by Gamelin Alexis's avatar Gamelin Alexis
Browse files

Merge branch 'numpy_bunch'

parents e32bfaf0 c670561f
No related branches found
No related tags found
No related merge requests found
...@@ -8,15 +8,15 @@ Module where particles, bunches and beams are described as objects. ...@@ -8,15 +8,15 @@ Module where particles, bunches and beams are described as objects.
""" """
import numpy as np import numpy as np
import pandas as pd
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from mbtrack2.tracking.parallel import Mpi
import seaborn as sns import seaborn as sns
import pandas as pd
from mbtrack2.tracking.parallel import Mpi
from scipy.constants import c, m_e, m_p, e from scipy.constants import c, m_e, m_p, e
class Particle: class Particle:
""" """
Define a particle object Define a particle object.
Attributes Attributes
---------- ----------
...@@ -47,35 +47,41 @@ class Proton(Particle): ...@@ -47,35 +47,41 @@ class Proton(Particle):
class Bunch: class Bunch:
""" """
Define a bunch Define a bunch object.
Parameters Parameters
---------- ----------
ring : Synchrotron object ring : Synchrotron object
mp_number : float, optional mp_number : float, optional
Macro-particle number Macro-particle number.
current : float, optional current : float, optional
Bunch current in [A] Bunch current in [A].
track_alive : bool, optional
If False, the code no longer take into account alive/dead particles.
Should be set to True if element such as apertures are used.
Can be set to False to gain a speed increase.
alive : bool, optional alive : bool, optional
If False, the bunch is defined as empty If False, the bunch is defined as empty.
Attributes Attributes
---------- ----------
mp_number : int mp_number : int
Macro-particle number Macro-particle number.
alive : array of bool of shape (mp_number,)
Array used to monitor which particle is dead or alive.
charge : float charge : float
Bunch charge in [C] Bunch charge in [C].
charge_per_mp : float charge_per_mp : float
Charge per macro-particle in [C] Charge per macro-particle in [C].
particle_number : int particle_number : int
Number of particles in the bunch Number of particles in the bunch.
current : float current : float
Bunch current in [A] Bunch current in [A].
mean : array of shape (6,) mean : array of shape (6,)
Mean position of alive particles for each coordinates Mean position of alive particles for each coordinates.
std : array of shape (6,) std : array of shape (6,)
Standard deviation of the position of alive particles for each Standard deviation of the position of alive particles for each
coordinates coordinates.
emit : array of shape (3,) emit : array of shape (3,)
Bunch emittance for each plane [1]. !!! -> Correct for long ? Bunch emittance for each plane [1]. !!! -> Correct for long ?
energy_change : series of shape (alive,) energy_change : series of shape (alive,)
...@@ -97,7 +103,8 @@ class Bunch: ...@@ -97,7 +103,8 @@ class Bunch:
Springer, Eq.(8.39) of p224. Springer, Eq.(8.39) of p224.
""" """
def __init__(self, ring, mp_number=1e3, current=1e-3, alive=True): def __init__(self, ring, mp_number=1e3, current=1e-3, track_alive=True,
alive=True):
self.ring = ring self.ring = ring
if not alive: if not alive:
...@@ -105,20 +112,22 @@ class Bunch: ...@@ -105,20 +112,22 @@ class Bunch:
current = 0 current = 0
self._mp_number = int(mp_number) self._mp_number = int(mp_number)
particles = {"x":np.zeros((self.mp_number,)), self.dtype = np.dtype([('x',np.float),
"xp":np.zeros((self.mp_number,)), ('xp',np.float),
"y":np.zeros((self.mp_number,)), ('y',np.float),
"yp":np.zeros((self.mp_number,)), ('yp',np.float),
"tau":np.zeros((self.mp_number,)), ('tau',np.float),
"delta":np.zeros((self.mp_number,)), ('delta',np.float)])
}
self.particles = pd.DataFrame(particles) self.particles = np.zeros(self.mp_number, self.dtype)
self.alive = pd.Series(np.ones((self.mp_number,),dtype=bool)) self.track_alive = track_alive
self.alive = np.ones((self.mp_number,),dtype=bool)
self.current = current self.current = current
if not alive: if not alive:
self.alive = pd.Series(np.zeros((self.mp_number,),dtype=bool)) self.alive = np.zeros((self.mp_number,),dtype=bool)
self._energy_change = pd.Series(np.zeros((self.mp_number,),dtype=float)) self._energy_change = np.zeros((self.mp_number,),dtype=float)
def __len__(self): def __len__(self):
"""Return the number of alive particles""" """Return the number of alive particles"""
...@@ -126,19 +135,25 @@ class Bunch: ...@@ -126,19 +135,25 @@ class Bunch:
def __getitem__(self, label): def __getitem__(self, label):
"""Return the columns label for alive particles""" """Return the columns label for alive particles"""
return self.particles.loc[self.alive, label] if self.track_alive is True:
return self.particles[label][self.alive]
else:
return self.particles[label]
def __setitem__(self, label, value): def __setitem__(self, label, value):
"""Set value to the columns label for alive particles""" """Set value to the columns label for alive particles"""
self.particles.loc[self.alive, label] = value if self.track_alive is True:
self.particles[label][self.alive] = value
else:
self.particles[label] = value
def __iter__(self): def __iter__(self):
"""Iterate over labels""" """Iterate over labels"""
return self[:].__iter__() return self.dtype.names.__iter__()
def __repr__(self): def __repr__(self):
"""Return representation of alive particles""" """Return representation of alive particles"""
return f'{self[:]!r}' return f'{pd.DataFrame(self[:])!r}'
@property @property
def mp_number(self): def mp_number(self):
...@@ -222,11 +237,17 @@ class Bunch: ...@@ -222,11 +237,17 @@ class Bunch:
the SynchrotronRadiation class to compute radiation damping. To be the SynchrotronRadiation class to compute radiation damping. To be
changed by Element objects which change the energy, for example RF changed by Element objects which change the energy, for example RF
cavities.""" cavities."""
return self._energy_change.loc[self.alive] if self.track_alive is True:
return self._energy_change[self.alive]
else:
return self._energy_change
@energy_change.setter @energy_change.setter
def energy_change(self, value): def energy_change(self, value):
self._energy_change.loc[self.alive] = value if self.track_alive is True:
self._energy_change[self.alive] = value
else:
self._energy_change = value
def init_gaussian(self, cov=None, mean=None, **kwargs): def init_gaussian(self, cov=None, mean=None, **kwargs):
""" """
...@@ -289,22 +310,28 @@ class Bunch: ...@@ -289,22 +310,28 @@ class Bunch:
Returns Returns
------- -------
bins : IntervalIndex of shape (n_bin,) bins : array of shape (n_bin,)
Bins where the particles are sorted. Bins where the particles are sorted.
sorted_index : Series of shape (self.mp_number,) sorted_index : array of shape (self.mp_number,)
Bin number of each macro-particles. Bin number of each macro-particles.
profile : Series of shape (n_bin,) profile : array of shape (n_bin - 1,)
Number of marco-particles in each bin. Number of marco-particles in each bin.
center : array of shape (n_bin - 1,)
Center of each bin.
""" """
bins = pd.interval_range(start=min(self[dimension]), bin_min = self[dimension].min()
end=max(self[dimension]), bin_min = min(bin_min*0.99, bin_min*1.01)
periods=n_bin, bin_max = self[dimension].max()
name=dimension) bin_max = max(bin_max*0.99, bin_max*1.01)
sorted_index = pd.cut(self[dimension], bins)
sorted_index = sorted_index.reindex(self.alive.index) bins = np.linspace(bin_min, bin_max, n_bin)
profile = sorted_index.value_counts().sort_index() center = (bins[1:] + bins[:-1])/2
return (bins, sorted_index, profile) sorted_index = np.searchsorted(bins, self[dimension], side='left')
sorted_index -= 1
profile = np.bincount(sorted_index, minlength=n_bin-1)
return (bins, sorted_index, profile, center)
def plot_profile(self, dimension="tau", n_bin=75): def plot_profile(self, dimension="tau", n_bin=75):
""" """
...@@ -318,10 +345,10 @@ class Bunch: ...@@ -318,10 +345,10 @@ class Bunch:
Number of bins. The default is 75. Number of bins. The default is 75.
""" """
bins, sorted_index, profile = self.binning(dimension, n_bin) bins, sorted_index, profile, center = self.binning(dimension, n_bin)
fig = plt.figure() fig = plt.figure()
ax = fig.gca() ax = fig.gca()
ax.plot(bins.mid, profile) ax.plot(center, profile)
def plot_phasespace(self, x_var="tau", y_var="delta", plot_type="j"): def plot_phasespace(self, x_var="tau", y_var="delta", plot_type="j"):
""" """
...@@ -476,7 +503,7 @@ class Beam: ...@@ -476,7 +503,7 @@ class Beam:
return distance return distance
def init_beam(self, filling_pattern, current_per_bunch=1e-3, def init_beam(self, filling_pattern, current_per_bunch=1e-3,
mp_per_bunch=1e3): mp_per_bunch=1e3, track_alive=True):
""" """
Initialize beam with a given filling pattern and marco-particle number Initialize beam with a given filling pattern and marco-particle number
per bunch. Then initialize the different bunches with a 6D gaussian per bunch. Then initialize the different bunches with a 6D gaussian
...@@ -496,6 +523,10 @@ class Beam: ...@@ -496,6 +523,10 @@ class Beam:
Current per bunch in [A] Current per bunch in [A]
mp_per_bunch : float, optional mp_per_bunch : float, optional
Macro-particle number per bunch Macro-particle number per bunch
track_alive : bool, optional
If False, the code no longer take into account alive/dead particles.
Should be set to True if element such as apertures are used.
Can be set to False to gain a speed increase.
""" """
if (len(filling_pattern) != self.ring.h): if (len(filling_pattern) != self.ring.h):
...@@ -507,13 +538,15 @@ class Beam: ...@@ -507,13 +538,15 @@ class Beam:
if filling_pattern.dtype == np.dtype("bool"): if filling_pattern.dtype == np.dtype("bool"):
for value in filling_pattern: for value in filling_pattern:
if value == True: if value == True:
bunch_list.append(Bunch(self.ring, mp_per_bunch, current_per_bunch)) bunch_list.append(Bunch(self.ring, mp_per_bunch,
current_per_bunch, track_alive))
elif value == False: elif value == False:
bunch_list.append(Bunch(self.ring, alive=False)) bunch_list.append(Bunch(self.ring, alive=False))
elif filling_pattern.dtype == np.dtype("float64"): elif filling_pattern.dtype == np.dtype("float64"):
for current in filling_pattern: for current in filling_pattern:
if current != 0: if current != 0:
bunch_list.append(Bunch(self.ring, mp_per_bunch, current)) bunch_list.append(Bunch(self.ring, mp_per_bunch,
current_per_bunch, track_alive))
elif current == 0: elif current == 0:
bunch_list.append(Bunch(self.ring, alive=False)) bunch_list.append(Bunch(self.ring, alive=False))
else: else:
......
...@@ -85,18 +85,19 @@ class WakePotential(Element): ...@@ -85,18 +85,19 @@ class WakePotential(Element):
""" """
# Get binning data # Get binning data
a, b, c = bunch.binning(n_bin=self.n_bin) a, b, c, d = bunch.binning(n_bin=self.n_bin)
self.bins = a self.bins = a
self.sorted_index = b self.sorted_index = b
self.profile = c self.profile = c
self.bin_size = self.bins.length self.center = d
self.bin_size = self.bins[1] - self.bins[0]
# Compute charge density # Compute charge density
self.rho = bunch.charge_per_mp*self.profile/(self.bin_size*bunch.charge) self.rho = bunch.charge_per_mp*self.profile/(self.bin_size*bunch.charge)
self.rho = np.array(self.rho) self.rho = np.array(self.rho)
# Compute time array # Compute time array
self.tau = np.array(self.bins.mid) self.tau = np.array(self.center)
self.dtau = self.tau[1] - self.tau[0] self.dtau = self.tau[1] - self.tau[0]
# Add N values before and after rho and tau # Add N values before and after rho and tau
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment