Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add particle species in Beam classes and update tracking methods #276

Open
wants to merge 42 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
213e7af
Add particle species for Beam classes
cr-xu Oct 7, 2024
cbcbd4c
Add particle mass in the `transfer_map` calculation
cr-xu Oct 14, 2024
b41181b
Update CI/CD pytest pipeline; Add benchmarking tests with different s…
cr-xu Oct 15, 2024
5f9c19d
Try using the 'classic' conda-solver
cr-xu Oct 15, 2024
89d1740
Try fixing conda with pip installs in github actions
cr-xu Oct 15, 2024
eb82c49
Try again conda
cr-xu Oct 15, 2024
9565b73
Separate windows tests, excluding bmad runs
cr-xu Oct 15, 2024
d49781e
Update test_tracking_different_species.py
cr-xu Oct 15, 2024
7cdb3f3
Merge branch 'master' into 231-add-particle-species-in-particlebeam
cr-xu Oct 16, 2024
489ebb2
Remove the screen reading from of the speed test
cr-xu Oct 16, 2024
30cf6a5
Update CHANGELOG and documentation
cr-xu Oct 16, 2024
a620f4e
Update docstring
cr-xu Oct 16, 2024
741232f
Merge branch 'master' into 231-add-particle-species-in-particlebeam
jank324 Oct 16, 2024
1a2976d
Update tests; Improve docstrings
cr-xu Oct 17, 2024
a3570ff
Merge branch 'master' into 231-add-particle-species-in-particlebeam
cr-xu Oct 22, 2024
691490b
Merge branch 'master' into 231-add-particle-species-in-particlebeam
jank324 Jan 16, 2025
ddcac5f
Some cleanup
jank324 Jan 16, 2025
d137d58
Clean up and merge most tests into one
jank324 Jan 16, 2025
10dd222
Fix flake8 errors because of undefined variables
jank324 Jan 16, 2025
262433f
Move `species` to `particles` module
jank324 Jan 16, 2025
de73cbd
Streamline species tests
jank324 Jan 16, 2025
e98ef0b
Merge branch 'master' into 231-add-particle-species-in-particlebeam
jank324 Jan 16, 2025
768871b
Fixes grammatical error in docstring
jank324 Jan 16, 2025
5005675
Fix issue that allowed accidental changing of the default species par…
jank324 Jan 16, 2025
12a3d89
Fix beam `__repr__`s, docstrings and other minor items
jank324 Jan 16, 2025
d74fc78
Return speed tests to original
jank324 Jan 16, 2025
09cd0d3
Fix broken references to `mass_eV`
jank324 Jan 16, 2025
a352ea0
Fix `Species` documentation render
jank324 Jan 16, 2025
c5e1ab8
Fix class location for saved incoming test beam
jank324 Jan 16, 2025
9275cd9
Properly deselect Bmad tests in Windows GitHub Action
jank324 Jan 16, 2025
7e8d653
Temporariliy deactive macOS test to see Ubuntu results
jank324 Jan 16, 2025
8ee1044
Revert "Temporariliy deactive macOS test to see Ubuntu results"
jank324 Jan 16, 2025
d54d63b
Get Tao usage in tests at least to a point where it doesn't crash all…
jank324 Jan 17, 2025
957b347
Fix Bmad particle species and element tests that don't fail because o…
jank324 Jan 17, 2025
6491a32
Fix segmentation fault failures in Bmad tests
jank324 Jan 17, 2025
6a63bb7
Finalise cleanup and unification into a single test of Bmad compare e…
jank324 Jan 17, 2025
1537d7b
Minor code formatting improvement
jank324 Jan 17, 2025
c582310
Force particle mass to be explicit in relativistic factor computation
jank324 Jan 17, 2025
992fa11
Fix failing tests
jank324 Jan 17, 2025
981f825
Fix weird random seed thing that caused broken test to succeed (since…
jank324 Jan 17, 2025
00c65a6
Remove fixed GitHub Actions random seed becasue it masked failing tests
jank324 Jan 17, 2025
5963602
Another attempt at fixing Tao import issue on Windows
jank324 Jan 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions .github/workflows/pytest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,28 @@ jobs:
include:
- os: macos-latest
python-version: "3.12"
- os: windows-latest
python-version: "3.12"
runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
- uses: conda-incubator/setup-miniconda@v3
with:
miniconda-version: "latest"
conda-solver: "classic"
python-version: ${{ matrix.python-version }}
- name: Install dependencies
activate-environment: test_environment
- name: Install Bmad and Pytao
shell: bash -el {0}
run: |
conda install -c conda-forge bmad
conda install -c conda-forge pytao
- name: Install pip dependencies
shell: bash -el {0}
run: |
python -m pip install --upgrade pip
pip install -e .[openpmd]
pip install -r test_requirements.txt
- name: Test with pytest
shell: bash -el {0}
run: |
pytest --seed 42
pytest
30 changes: 30 additions & 0 deletions .github/workflows/pytest_windows.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: pytest_windows

on:
push:
branches: [master]
pull_request:
branches: [master]

jobs:
build:
strategy:
matrix:
os: [windows-latest]
python-version: ["3.12"]
runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e .
pip install -r test_requirements.txt
- name: Test with pytest
run: |
pytest --deselect tests/test_compare_bmad.py
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
### 🚀 Features

- `ParticleBeam` now supports importing from and exporting to [openPMD-beamphysics](https://github.com/ChristopherMayes/openPMD-beamphysics) HDF5 files and `ParticleGroup` objects. This allows for easy conversion to and from other file formats supported by openPMD-beamphysics. (see #305, #320) (@cr-xu, @Hespe)
- Add the option to choose the particle species `Species` for `Beam` classes (see #276) (@cr-xu, @jank324)

### 🐛 Bug fixes

Expand Down
2 changes: 1 addition & 1 deletion cheetah/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@
Undulator,
VerticalCorrector,
)
from .particles import Beam, ParameterBeam, ParticleBeam # noqa: F401
from .particles import Beam, ParameterBeam, ParticleBeam, Species # noqa: F401
4 changes: 3 additions & 1 deletion cheetah/accelerator/aperture.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ def __init__(
def is_skippable(self) -> bool:
return not self.is_active

def transfer_map(self, energy: torch.Tensor) -> torch.Tensor:
def transfer_map(
self, energy: torch.Tensor, particle_mass_eV: torch.Tensor
) -> torch.Tensor:
device = self.x_max.device
dtype = self.x_max.dtype

Expand Down
4 changes: 3 additions & 1 deletion cheetah/accelerator/bpm.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ def __init__(self, is_active: bool = False, name: Optional[str] = None) -> None:
def is_skippable(self) -> bool:
return not self.is_active

def transfer_map(self, energy: torch.Tensor) -> torch.Tensor:
def transfer_map(
self, energy: torch.Tensor, particle_mass_eV: torch.Tensor
) -> torch.Tensor:
return torch.eye(7, device=energy.device, dtype=energy.dtype).repeat(
(*energy.shape, 1, 1)
)
Expand Down
38 changes: 23 additions & 15 deletions cheetah/accelerator/cavity.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import torch
from matplotlib.patches import Rectangle
from scipy import constants
from scipy.constants import physical_constants

from cheetah.accelerator.element import Element
from cheetah.particles import Beam, ParameterBeam, ParticleBeam
Expand All @@ -17,15 +16,15 @@

generate_unique_name = UniqueNameGenerator(prefix="unnamed_element")

electron_mass_eV = physical_constants["electron mass energy equivalent in MeV"][0] * 1e6


class Cavity(Element):
"""
Accelerating cavity in a particle accelerator.

:param length: Length in meters.
:param voltage: Voltage of the cavity in volts.
:param voltage: Voltage of the cavity in volts. NOTE: This assumes the effective
voltage. If the particle does not have unit charge, the voltage needs to be
scaled properly.
:param phase: Phase of the cavity in degrees.
:param frequency: Frequency of the cavity in Hz.
:param name: Unique identifier of the element.
Expand Down Expand Up @@ -67,14 +66,17 @@ def is_active(self) -> bool:
def is_skippable(self) -> bool:
return not self.is_active

def transfer_map(self, energy: torch.Tensor) -> torch.Tensor:
def transfer_map(
self, energy: torch.Tensor, particle_mass_eV: torch.Tensor
) -> torch.Tensor:
return torch.where(
(self.voltage != 0).unsqueeze(-1).unsqueeze(-1),
self._cavity_rmatrix(energy),
self._cavity_rmatrix(energy, particle_mass_eV),
base_rmatrix(
length=self.length,
k1=torch.zeros_like(self.length),
hx=torch.zeros_like(self.length),
particle_mass_eV=particle_mass_eV,
tilt=torch.zeros_like(self.length),
energy=energy,
),
Expand All @@ -99,11 +101,13 @@ def _track_beam(self, incoming: Beam) -> Beam:
Track particles through the cavity. The input can be a `ParameterBeam` or a
`ParticleBeam`.
"""
gamma0, igamma2, beta0 = compute_relativistic_factors(incoming.energy)
gamma0, igamma2, beta0 = compute_relativistic_factors(
incoming.energy, incoming.species.mass_eV
)

phi = torch.deg2rad(self.phase)

tm = self.transfer_map(incoming.energy)
tm = self.transfer_map(incoming.energy, incoming.species.mass_eV)
if isinstance(incoming, ParameterBeam):
outgoing_mu = torch.matmul(tm, incoming._mu.unsqueeze(-1)).squeeze(-1)
outgoing_cov = torch.matmul(
Expand All @@ -120,7 +124,9 @@ def _track_beam(self, incoming: Beam) -> Beam:
if torch.any(incoming.energy + delta_energy > 0):
k = 2 * torch.pi * self.frequency / constants.speed_of_light
outgoing_energy = incoming.energy + delta_energy
gamma1, _, beta1 = compute_relativistic_factors(outgoing_energy)
gamma1, _, beta1 = compute_relativistic_factors(
outgoing_energy, incoming.species.mass_eV
)

if isinstance(incoming, ParameterBeam):
outgoing_mu[..., 5] = incoming._mu[..., 5] * incoming.energy * beta0 / (
Expand Down Expand Up @@ -151,7 +157,7 @@ def _track_beam(self, incoming: Beam) -> Beam:
- torch.cos(phi).unsqueeze(-1)
)

dgamma = self.voltage / electron_mass_eV
dgamma = self.voltage / incoming.species.mass_eV
if torch.any(delta_energy > 0):
T566 = (
self.length
Expand Down Expand Up @@ -237,16 +243,18 @@ def _track_beam(self, incoming: Beam) -> Beam:
)
return outgoing

def _cavity_rmatrix(self, energy: torch.Tensor) -> torch.Tensor:
def _cavity_rmatrix(
self, energy: torch.Tensor, particle_mass_eV: torch.Tensor
) -> torch.Tensor:
"""Produces an R-matrix for a cavity when it is on, i.e. voltage > 0.0."""
factory_kwargs = {"device": self.length.device, "dtype": self.length.dtype}

phi = torch.deg2rad(self.phase)
delta_energy = self.voltage * torch.cos(phi)
# Comment from Ocelot: Pure pi-standing-wave case
eta = torch.tensor(1.0, **factory_kwargs)
Ei = energy / electron_mass_eV
Ef = (energy + delta_energy) / electron_mass_eV
Ei = energy / particle_mass_eV
Ef = (energy + delta_energy) / particle_mass_eV
Ep = (Ef - Ei) / self.length # Derivative of the energy
assert torch.all(Ei > 0), "Initial energy must be larger than 0"

Expand Down Expand Up @@ -296,14 +304,14 @@ def _cavity_rmatrix(self, energy: torch.Tensor) -> torch.Tensor:
* self.length
* beta0
* self.voltage
/ electron_mass_eV
/ particle_mass_eV
* torch.sin(phi)
* (g0 * g1 * (beta0 * beta1 - 1) + 1)
/ (beta1 * g1 * (g0 - g1) ** 2)
)

r66 = Ei / Ef * beta0 / beta1
r65 = k * torch.sin(phi) * self.voltage / (Ef * beta1 * electron_mass_eV)
r65 = k * torch.sin(phi) * self.voltage / (Ef * beta1 * particle_mass_eV)

# Make sure that all matrix elements have the same shape
r11, r12, r21, r22, r55_cor, r56, r65, r66 = torch.broadcast_tensors(
Expand Down
23 changes: 19 additions & 4 deletions cheetah/accelerator/custom_transfer_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,27 @@ def from_merging_elements(
" incorrect tracking results."
)

device = elements[0].transfer_map(incoming_beam.energy).device
dtype = elements[0].transfer_map(incoming_beam.energy).dtype
device = (
elements[0]
.transfer_map(incoming_beam.energy, incoming_beam.species.mass_eV)
.device
)
dtype = (
elements[0]
.transfer_map(incoming_beam.energy, incoming_beam.species.mass_eV)
.dtype
)

tm = torch.eye(7, device=device, dtype=dtype).repeat(
(*incoming_beam.energy.shape, 1, 1)
)
for element in elements:
tm = torch.matmul(element.transfer_map(incoming_beam.energy), tm)
tm = torch.matmul(
element.transfer_map(
incoming_beam.energy, incoming_beam.species.mass_eV
),
tm,
)
incoming_beam = element.track(incoming_beam)

combined_length = sum(element.length for element in elements)
Expand All @@ -80,7 +93,9 @@ def from_merging_elements(
tm, length=combined_length, device=device, dtype=dtype, name=combined_name
)

def transfer_map(self, energy: torch.Tensor) -> torch.Tensor:
def transfer_map(
self, energy: torch.Tensor, particle_mass_eV: torch.Tensor
) -> torch.Tensor:
return self.predefined_transfer_map

@property
Expand Down
22 changes: 9 additions & 13 deletions cheetah/accelerator/dipole.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import matplotlib.pyplot as plt
import torch
from matplotlib.patches import Rectangle
from scipy.constants import physical_constants

from cheetah.accelerator.element import Element
from cheetah.particles import Beam, ParticleBeam
Expand All @@ -12,8 +11,6 @@

generate_unique_name = UniqueNameGenerator(prefix="unnamed_element")

electron_mass_eV = physical_constants["electron mass energy equivalent in MeV"][0] * 1e6


class Dipole(Element):
"""
Expand Down Expand Up @@ -189,10 +186,9 @@ def _track_bmadx(self, incoming: ParticleBeam) -> ParticleBeam:
py = incoming.py
tau = incoming.tau
delta = incoming.p
mc2 = incoming.species.mass_eV

z, pz, p0c = bmadx.cheetah_to_bmad_z_pz(
tau, delta, incoming.energy, electron_mass_eV
)
z, pz, p0c = bmadx.cheetah_to_bmad_z_pz(tau, delta, incoming.energy, mc2)

# Begin Bmad-X tracking
x, px, y, py = bmadx.offset_particle_set(
Expand All @@ -207,9 +203,7 @@ def _track_bmadx(self, incoming: ParticleBeam) -> ParticleBeam:

if self.fringe_at == "entrance" or self.fringe_at == "both":
px, py = self._bmadx_fringe_linear("entrance", x, px, y, py)
x, px, y, py, z, pz = self._bmadx_body(
x, px, y, py, z, pz, p0c, electron_mass_eV
)
x, px, y, py, z, pz = self._bmadx_body(x, px, y, py, z, pz, p0c, mc2)
if self.fringe_at == "exit" or self.fringe_at == "both":
px, py = self._bmadx_fringe_linear("exit", x, px, y, py)

Expand All @@ -225,9 +219,7 @@ def _track_bmadx(self, incoming: ParticleBeam) -> ParticleBeam:
# End of Bmad-X tracking

# Convert back to Cheetah coordinates
tau, delta, ref_energy = bmadx.bmad_to_cheetah_z_pz(
z, pz, p0c, electron_mass_eV
)
tau, delta, ref_energy = bmadx.bmad_to_cheetah_z_pz(z, pz, p0c, mc2)

# Broadcast to align their shapes so that they can be stacked
x, px, y, py, tau, delta = torch.broadcast_tensors(x, px, y, py, tau, delta)
Expand All @@ -241,6 +233,7 @@ def _track_bmadx(self, incoming: ParticleBeam) -> ParticleBeam:
survival_probabilities=incoming.survival_probabilities,
device=incoming.particles.device,
dtype=incoming.particles.dtype,
species=incoming.species,
)
return outgoing_beam

Expand Down Expand Up @@ -374,7 +367,9 @@ def _bmadx_fringe_linear(

return px_f, py_f

def transfer_map(self, energy: torch.Tensor) -> torch.Tensor:
def transfer_map(
self, energy: torch.Tensor, particle_mass_eV: torch.Tensor
) -> torch.Tensor:
device = self.length.device
dtype = self.length.dtype

Expand All @@ -386,6 +381,7 @@ def transfer_map(self, energy: torch.Tensor) -> torch.Tensor:
length=self.length,
k1=self.k1,
hx=self.hx,
particle_mass_eV=particle_mass_eV,
tilt=torch.zeros_like(self.length),
energy=energy,
) # Tilt is applied after adding edges
Expand Down
Loading
Loading