"""Define objects to store initial state/trajectory of a particle.
- :class:`ParticleInitialState` is just here to save the position and
energy of a particle at the entrance of the linac. Saved as an
:class:`.ListOfElements` attribute.
- :class:`ParticleFullTrajectory` saves the energy, phase, position of a
particle along the linac. As a single :class:`ParticleInitialState` can
lead to several :class:`ParticleFullTrajectory` (according to size of the
mesh, the solver, etc), :class:`.ParticleFullTrajectory` are stored in
:class:`.SimulationOutput`.
"""
from dataclasses import dataclass
from typing import Any
import numpy as np
from numpy.typing import NDArray
import lightwin.physics.converters as convert
from lightwin.tracewin_utils.interface import particle_initial_state_to_command
from lightwin.util.helper import (
range_vals_object,
recursive_getter,
recursive_items,
)
from lightwin.util.typing import GETTABLE_PARTICLE_T, BeamKwargs
[docs]
@dataclass
class ParticleInitialState:
"""Hold the initial energy/phase of a particle, and if it is synchronous.
It is used for :class:`.ListOfElements` attribute.
"""
w_kin: float
phi_abs: float
z_in: float
synchronous: bool
@property
def tracewin_command(self) -> list[str]:
"""Create the energy and phase arguments for TraceWin command."""
args = (self.w_kin,)
_tracewin_command = particle_initial_state_to_command(*args)
return _tracewin_command
[docs]
@dataclass
class ParticleFullTrajectory:
r"""Hold the full energy, phase, etc of a particle.
It is stored in a :class:`.SimulationOutput`.
Phase is defined as:
.. math::
\phi = \omega_{0,\,\mathrm{bunch}} t
while in :class:`.Field` it is:
.. math::
\phi = \omega_{0,\,\mathrm{rf}} t
"""
w_kin: NDArray | list
phi_abs: NDArray | list
synchronous: bool
beam: BeamKwargs
[docs]
def __post_init__(self):
"""Ensure that LightWin has everything it needs, with proper format."""
if isinstance(self.phi_abs, list):
self.phi_abs = np.array(self.phi_abs)
if isinstance(self.w_kin, list):
self.w_kin = np.array(self.w_kin)
self.gamma = convert.energy(self.w_kin, "kin to gamma", **self.beam)
self.beta: NDArray
[docs]
def __str__(self) -> str:
"""Show amplitude of phase and energy."""
out = "\tParticleFullTrajectory:\n"
out += "\t\t" + range_vals_object(self, "w_kin")
out += "\t\t" + range_vals_object(self, "phi_abs")
return out
@property
def tracewin_command(self) -> list[str]:
"""Raise an error, this method should be called from InitialPart."""
raise OSError("This method should not be used from here.")
[docs]
def compute_reduced_velocity(self) -> None:
r"""Compute and store :math:`\beta`."""
self.beta = convert.energy(self.gamma, "gamma to beta", **self.beam)
[docs]
def has(self, key: str) -> bool:
"""Tell if the required attribute is in this class or its subfields."""
return key in recursive_items(vars(self))
[docs]
def get(
self,
*keys: GETTABLE_PARTICLE_T,
to_numpy: bool = True,
none_to_nan: bool = False,
to_deg: bool = False,
**kwargs: Any,
) -> Any:
"""Get attributes from this class or its nested attributes.
Parameters
----------
*keys :
Names of the desired attributes.
to_numpy :
Convert list outputs to NumPy arrays.
none_to_nan :
Convert ``None`` values to ``np.nan``.
to_deg :
Convert phase attributes (containing "phi") to degrees.
**kwargs :
Passed to recursive_getter.
Returns
-------
Any
A single value if one key is given, or a tuple of values.
"""
results = []
for key in keys:
value = (
recursive_getter(key, vars(self), **kwargs)
if self.has(key)
else None
)
if value is None and none_to_nan:
value = np.nan
if to_deg and value is not None and "phi" in key:
value = np.rad2deg(value)
if to_numpy and isinstance(value, list):
value = np.array(value)
elif not to_numpy and isinstance(value, np.ndarray):
value = value.tolist()
results.append(value)
return results[0] if len(results) == 1 else tuple(results)
# def create_rand_particles(e_0_mev):
# """Create two random particles."""
# delta_z = 1e-4
# delta_E = 1e-4
# rand_1 = Particle(-1.42801442802603928417e-04,
# 1.66094219207764304258e+01,)
# rand_2 = Particle(2.21221539793564048182e-03,
# 1.65923664093018210508e+01,)
# # rand_1 = Particle(
# # random.uniform(0., delta_z * .5),
# # random.uniform(e_0_mev, e_0_mev + delta_E * .5),
# # omega0_bunch)
# # rand_2 = Particle(
# # random.uniform(-delta_z * .5, 0.),
# # random.uniform(e_0_mev - delta_E * .5, e_0_mev),
# # omega0_bunch)
# return rand_1, rand_2