Skip to content

Commit

Permalink
Merge branch 'dev' into feature/new_gridless_model
Browse files Browse the repository at this point in the history
  • Loading branch information
AngelFP committed May 15, 2024
2 parents a991999 + b45e9cf commit 8f1d43f
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ __pycache__
/examples/tests.py
/old_stuff

# Test output
/tests_output

# IDEs
/.vscode

Expand Down
50 changes: 50 additions & 0 deletions tests/test_field_quadrupole.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import copy

import numpy as np
import scipy.constants as ct

from wake_t.beamline_elements import FieldQuadrupole, Quadrupole
from wake_t.utilities.bunch_generation import get_gaussian_bunch_from_twiss


def test_field_vs_tm_quadrupole():
"""
This test checks that the FieldElement-based quadrupole (FieldQuadrupole)
and the TM quadrupole (Quadrupole) produce similar results.
"""

emitt_nx = emitt_ny = 1e-6 # m
beta_x = beta_y = 1. # m
s_t = 100. # fs
gamma_avg = 1000
ene_spread = 0.1 # %
q_bunch = 30 # pC
xi_avg = 0. # m
n_part = 1e4
bunch_1 = get_gaussian_bunch_from_twiss(
en_x=emitt_nx, en_y=emitt_ny, a_x=0, a_y=0, b_x=beta_x, b_y=beta_y,
ene=gamma_avg, ene_sp=ene_spread, s_t=s_t, xi_c=xi_avg,
q_tot=q_bunch, n_part=n_part, name='elec_bunch')

bunch_2 = copy.deepcopy(bunch_1)

foc_strength = 100 # T/m
quadrupole_length = 0.05 # m
k1 = foc_strength * ct.e / ct.m_e / ct.c / gamma_avg

field_quadrupole = FieldQuadrupole(quadrupole_length, foc_strength)
tm_quadrupole = Quadrupole(quadrupole_length, k1)

field_quadrupole.track(bunch_1)
tm_quadrupole.track(bunch_2)

np.testing.assert_allclose(bunch_1.x, bunch_2.x, rtol=1e-3, atol=1e-7)
np.testing.assert_allclose(bunch_1.y, bunch_2.y, rtol=1e-3, atol=1e-7)
np.testing.assert_allclose(bunch_1.xi, bunch_2.xi, rtol=1e-3, atol=1e-7)
np.testing.assert_allclose(bunch_1.px, bunch_2.px, rtol=1e-3, atol=1e-3)
np.testing.assert_allclose(bunch_1.py, bunch_2.py, rtol=1e-3, atol=1e-3)
np.testing.assert_allclose(bunch_1.pz, bunch_2.pz, rtol=1e-3, atol=1e-3)


if __name__ == '__main__':
test_field_vs_tm_quadrupole()
4 changes: 3 additions & 1 deletion wake_t/beamline_elements/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
from .active_plasma_lens import ActivePlasmaLens
from .beamline import Beamline
from .field_element import FieldElement
from .field_quadrupole import FieldQuadrupole


__all__ = [
'Drift', 'Dipole', 'Quadrupole', 'Sextupole', 'PlasmaStage', 'PlasmaRamp',
'ActivePlasmaLens', 'Beamline', 'FieldElement', 'TMElement']
'ActivePlasmaLens', 'Beamline', 'FieldElement', 'TMElement',
'FieldQuadrupole']
71 changes: 71 additions & 0 deletions wake_t/beamline_elements/field_quadrupole.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from typing import Literal, Optional

import numpy as np
import scipy.constants as ct

from .plasma_stage import DtBunchType
from .field_element import FieldElement
from wake_t.physics_models.em_fields.quadrupole import QuadrupoleField


class FieldQuadrupole(FieldElement):
"""
Class defining a quadrupole as a field element.
Parameters
----------
length : float
Length of the quadrupole lens in :math:`m`.
foc_strength : float
Focusing strength of the quadrupole in :math:`T/m`. Defined so
that a positive value is focusing for electrons in the :math:`x`
plane and defocusing in the :math:`y` plane.
dt_bunch : float, str, or list of float and str
The time step for evolving the particle bunches. If ``'auto'``, it will
be automatically set to :math:`dt = T/(10*2*pi)`, where T is the
betatron period of the particle with the lowest energy in the bunch.
A list of values can also be provided. In this case, the list
should have the same order as the list of bunches given to the
``track`` method.
bunch_pusher : str
The pusher used to evolve the particle bunches in time within
the specified fields. Possible values are ``'rk4'`` (Runge-Kutta
method of 4th order) or ``'boris'`` (Boris method).
n_out : int, optional
Number of times along the lens in which the particle distribution
should be returned (A list with all output bunches is returned
after tracking).
name : str, optional
Name of the quadrupole. This is only used for displaying the
progress bar during tracking. By default, 'quadrupole'
"""

def __init__(
self,
length: float,
foc_strength: float,
dt_bunch: Optional[DtBunchType] = 'auto',
bunch_pusher: Literal['boris', 'rk4'] = 'boris',
n_out: Optional[int] = 1,
name: Optional[str] = 'quadrupole',
) -> None:
self.foc_strength = foc_strength
super().__init__(
length=length,
dt_bunch=dt_bunch,
bunch_pusher=bunch_pusher,
n_out=n_out,
name=name,
fields=[QuadrupoleField(foc_strength)],
auto_dt_bunch=self._get_optimized_dt,
)

def _get_optimized_dt(self, beam):
""" Get tracking time step. """
# Get minimum gamma in the bunch (assumes px,py << pz).
q_over_m = beam.q_species / beam.m_species
min_gamma = np.sqrt(np.min(beam.pz)**2 + 1)
w_x = np.sqrt(np.abs(q_over_m*ct.c * self.foc_strength/min_gamma))
T_x = 1/w_x
dt = 0.1*T_x
return dt
41 changes: 41 additions & 0 deletions wake_t/physics_models/em_fields/quadrupole.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
""" Defines a magnetic field of a quadrupole """

from wake_t.fields.analytical_field import AnalyticalField
from wake_t.utilities.numba import prange


def b_x(x, y, z, t, bx, constants):
"""B_x component."""
k = - constants[0]
for i in prange(x.shape[0]):
bx[i] += k * y[i]


def b_y(x, y, z, t, by, constants):
"""B_y component."""
k = - constants[0]
for i in prange(x.shape[0]):
by[i] += k * x[i]


class QuadrupoleField(AnalyticalField):
"""Defines a field of a magnetic quadrupole of constant focusing gradient
`k`.
In Cartesian coordinates, the field is given by:
```
b_x = - k * y
b_y = - k * x
```
When `k > 0`, it corresponds to focussing electrons in the `x` direction
and defocussing in `y`. When `k < 0`, the result is the opposite.
Parameters
----------
foc_gradient : float
Uniform focusing gradient in T/m.
"""

def __init__(self, foc_gradient):
super().__init__(b_x=b_x, b_y=b_y, constants=[foc_gradient])

0 comments on commit 8f1d43f

Please sign in to comment.