"""Define :class:`Envelope3D`, an envelope solver."""
import logging
from collections.abc import Collection
from pathlib import Path
from lightwin.beam_calculation.beam_calculator import BeamCalculator
from lightwin.beam_calculation.envelope_3d.beam_parameters_factory import (
BeamParametersFactoryEnvelope3D,
)
from lightwin.beam_calculation.envelope_3d.element_envelope3d_parameters_factory import (
ElementEnvelope3DParametersFactory,
)
from lightwin.beam_calculation.envelope_3d.simulation_output_factory import (
SimulationOutputFactoryEnvelope3D,
)
from lightwin.beam_calculation.envelope_3d.transfer_matrix_factory import (
TransferMatrixFactoryEnvelope3D,
)
from lightwin.beam_calculation.envelope_3d.util import ENVELOPE3D_METHODS_T
from lightwin.beam_calculation.simulation_output.simulation_output import (
SimulationOutput,
)
from lightwin.core.accelerator.accelerator import Accelerator
from lightwin.core.elements.field_maps.cavity_settings import CavitySettings
from lightwin.core.list_of_elements.list_of_elements import ListOfElements
from lightwin.failures.set_of_cavity_settings import SetOfCavitySettings
from lightwin.physics.synchronous_phases import (
PHI_S_MODELS,
SYNCHRONOUS_PHASE_FUNCTIONS,
)
from lightwin.util.typing import (
EXPORT_PHASES_T,
REFERENCE_PHASE_POLICY_T,
BeamKwargs,
)
[docs]
class Envelope3D(BeamCalculator):
"""A 3D envelope solver.
As transverse effects are generally not predominant, I do not use this
solver very often and a lot of elements are not implemented.
The current list of explicitly supported elements is:
.. configkeys:: lightwin.beam_calculation.envelope_3d.element_envelope3d_\
parameters_factory.PARAMETERS_3D
:n_cols: 2
The default behavior when an element in the input ``DAT`` file is not
recognized, is to issue a warning and replace this element by a ``DRIFT``.
Do not hesitate to file an |issue|_ if you need me to implement some
elements.
"""
flag_cython = False
[docs]
def __init__(
self,
*,
reference_phase_policy: REFERENCE_PHASE_POLICY_T,
out_folder: Path | str,
default_field_map_folder: Path | str,
beam_kwargs: BeamKwargs,
export_phase: EXPORT_PHASES_T,
phi_s_definition: PHI_S_MODELS = "historical",
n_steps_per_cell: int,
method: ENVELOPE3D_METHODS_T = "RK4",
**kwargs,
) -> None:
"""Set the proper motion integration function, according to inputs."""
self.n_steps_per_cell = n_steps_per_cell
self.method: ENVELOPE3D_METHODS_T = method
self._phi_s_definition: PHI_S_MODELS = phi_s_definition
self._phi_s_func = SYNCHRONOUS_PHASE_FUNCTIONS[self._phi_s_definition]
super().__init__(
reference_phase_policy=reference_phase_policy,
out_folder=out_folder,
default_field_map_folder=default_field_map_folder,
beam_kwargs=beam_kwargs,
export_phase=export_phase,
**kwargs,
)
self.beam_parameters_factory = BeamParametersFactoryEnvelope3D(
self.is_a_3d_simulation,
self.is_a_multiparticle_simulation,
beam_kwargs=self._beam_kwargs,
)
self.transfer_matrix_factory = TransferMatrixFactoryEnvelope3D(
self.is_a_3d_simulation
)
[docs]
def _set_up_specific_factories(self) -> None:
"""Set up the factories specific to the :class:`.BeamCalculator`.
This method is called in the :meth:`.BeamCalculator.__init__`, hence it
appears only in the base :class:`.BeamCalculator`.
"""
self.simulation_output_factory = SimulationOutputFactoryEnvelope3D(
_is_3d=self.is_a_3d_simulation,
_is_multipart=self.is_a_multiparticle_simulation,
_solver_id=self.id,
_beam_kwargs=self._beam_kwargs,
out_folder=self.out_folder,
)
self.beam_calc_parameters_factory = ElementEnvelope3DParametersFactory(
method=self.method,
n_steps_per_cell=self.n_steps_per_cell,
solver_id=self.id,
beam_kwargs=self._beam_kwargs,
phi_s_definition=self._phi_s_definition,
)
[docs]
def run(
self,
elts: ListOfElements,
update_reference_phase: bool = False,
**kwargs,
) -> SimulationOutput:
"""Compute beam propagation in 3D, envelope calculation.
Parameters
----------
elts :
List of elements in which the beam must be propagated.
update_reference_phase :
To change the reference phase of cavities when it is different from
the one asked in the ``TOML``. To use after the first calculation,
if :attr:`.BeamCalculator.reference_phase_policy` does not align
with :attr:`.CavitySettings.reference`.
Returns
-------
Holds energy, phase, transfer matrices (among others) packed into a
single object.
"""
return super().run(elts, update_reference_phase, **kwargs)
[docs]
def run_with_this(
self,
set_of_cavity_settings: SetOfCavitySettings | None,
elts: ListOfElements,
use_a_copy_for_nominal_settings: bool = True,
) -> SimulationOutput:
"""Compute beam propagation with non-nominal settings.
Parameters
----------
set_of_cavity_settings :
The new cavity settings to try. If it is None, then the cavity
settings are taken from the FieldMap objects.
elts :
List of elements in which the beam must be propagated.
use_a_copy_for_nominal_settings :
To copy the nominal :class:`.CavitySettings` and avoid altering
their nominal counterpart. Set it to True during optimisation, to
False when you want to keep the current settings.
Returns
-------
Holds energy, phase, transfer matrices (among others) packed into a
single object.
"""
single_elts_results = []
w_kin = elts.w_kin_in
phi_abs = elts.phi_abs_in
set_of_cavity_settings = SetOfCavitySettings.from_incomplete_set(
set_of_cavity_settings,
elts.l_cav,
use_a_copy_for_nominal_settings=use_a_copy_for_nominal_settings,
)
for elt in elts:
cavity_settings = set_of_cavity_settings.get(elt, None)
_store_entry_phase_in_settings(phi_abs, cavity_settings)
func = elt.beam_calc_param[self.id].transf_mat_function_wrapper
elt_results = func(w_kin=w_kin, cavity_settings=cavity_settings)
if cavity_settings is not None:
v_cav_mv, phi_s = self._compute_cavity_parameters(elt_results)
cavity_settings.v_cav_mv = v_cav_mv
cavity_settings.phi_s = phi_s
single_elts_results.append(elt_results)
phi_abs += elt_results["phi_rel"][-1]
w_kin = elt_results["w_kin"][-1]
simulation_output = self._generate_simulation_output(
elts, single_elts_results, set_of_cavity_settings
)
return simulation_output
[docs]
def post_optimisation_run_with_this(
self,
optimized_cavity_settings: SetOfCavitySettings,
full_elts: ListOfElements,
**specific_kwargs,
) -> SimulationOutput:
"""Run Envelope3D with optimized cavity settings.
With this solver, we have nothing to do, nothing to update. Just call
the regular `run_with_this` method.
"""
simulation_output = self.run_with_this(
optimized_cavity_settings,
full_elts,
use_a_copy_for_nominal_settings=False,
**specific_kwargs,
)
return simulation_output
[docs]
def init_solver_parameters(self, accelerator: Accelerator) -> None:
"""Create the number of steps, meshing, transfer functions for elts.
The solver parameters are stored in the :class:`.Element`'s
``beam_calc_param``.
Parameters
----------
Object which :class:`.ListOfElements` must be initialized.
"""
elts = accelerator.elts
position = 0.0
index = 0
for elt in elts:
if self.id in elt.beam_calc_param:
logging.debug(
f"Solver already initialized for {elt = }. I will skip "
f"solver param initialisation {elts[0]} to {elts[-1]}"
)
return
solver_param = self.beam_calc_parameters_factory.run(elt)
elt.beam_calc_param[self.id] = solver_param
position, index = solver_param.set_absolute_meshes(position, index)
logging.debug(f"Initialized solver param for {elts[0]} to {elts[-1]}")
return
@property
def is_a_multiparticle_simulation(self) -> bool:
"""Return False."""
return False
@property
def is_a_3d_simulation(self) -> bool:
"""Return True."""
return True
[docs]
def _compute_cavity_parameters(self, results: dict) -> tuple[float, float]:
"""Compute the cavity parameters by calling ``_phi_s_func``.
Parameters
----------
results :
The dictionary of results as returned by the transfer matrix
function wrapper.
Returns
-------
Accelerating voltage in :unit:`MV` and synchronous phase in
:unit:`rad`. If the cavity is failed, two ``np.nan`` are returned.
"""
v_cav_mv, phi_s = self._phi_s_func(**results)
return v_cav_mv, phi_s
[docs]
def _store_entry_phase_in_settings(
phi_bunch_abs: float,
cavity_settings: CavitySettings | Collection[CavitySettings] | None,
) -> None:
"""Set entry phase."""
if cavity_settings is None:
return
if isinstance(cavity_settings, CavitySettings):
cavity_settings = (cavity_settings,)
for settings in cavity_settings:
settings.phi_bunch = phi_bunch_abs
return