Source code for pyhdtoolkit.cpymadtools.lhc._setup

"""
.. _lhc-setup:

**Setup Utilities**

The functions below are setup utilities for the ``LHC``,
to easily get simulations ready.
"""

from __future__ import annotations

from pathlib import Path

from cpymad.madx import Madx
from loguru import logger

from pyhdtoolkit.cpymadtools.constants import LHC_CROSSING_SCHEMES

_BEAM_FOR_B4: int = 2  # LHC beam 4 uses lhcb2 sequence
_RUN2: int = 2

# ----- Setup Utilities ----- #


[docs] def prepare_lhc_run2( opticsfile: str, beam: int = 1, use_b4: bool = False, energy: float = 6500, slicefactor: int | None = None, **kwargs ) -> Madx: """ .. versionadded:: 1.0.0 Returns a prepared default ``LHC`` setup for the given *opticsfile*, for a Run 2 setup. Both beams are made with a default Run 2 configuration, and the ``lhcb`` sequence for the given beam is re-cycled from ``MSIA.EXIT.B{beam}`` as in the ``OMC`` model_creator, and then ``USE``-d. Specific variable settings can be given as keyword arguments. Important --------- As this is a Run 2 setup, it is assumed that files are organised in the typical setup as found on ``AFS``. The sequence file will be looked for as a relative location from the optics file: it is assumed that next to the sequence file is a **PROTON** or **ION** folder with the opticsfiles. Note ---- Matching is **not** performed by this function and should be taken care of by the user, but the working point should be set by the definitions in the *opticsfile*. Parameters ---------- opticsfile : str The relative string path or a `Path` object to the opticsfile location. This will be used to determine the location of the sequence file, see the admonition above. beam : int The beam to set up for. Defaults to beam 1. use_b4 : bool If `True`, the lhcb4 sequence file will be used. This is the beam 2 sequence but for tracking purposes. Defaults to `False`. energy : float The beam energy to set up for, in [GeV]. Defaults to 6500. slicefactor : int, optional If provided, the sequence will be sliced and made thin. Defaults to `None`, which leads to an unsliced sequence. **kwargs If `echo` or `warn` are found in the keyword arguments they will be transmitted as options to ``MAD-X`` (by default these two are given as `False`). Any other keyword argument is transmitted to the `~cpymad.madx.Madx` creation call. Returns ------- cpymad.madx.Madx An instanciated `~cpymad.madx.Madx` object with the required configuration. Example ------- .. code-block:: python madx = prepare_lhc_run2( "/afs/cern.ch/eng/lhc/optics/runII/2018/PROTON/opticsfile.22", beam=2, stdout=True, ) """ if use_b4 and beam != _BEAM_FOR_B4: logger.error("Cannot use beam 4 sequence file for beam 1") msg = "Cannot use beam 4 sequence file for beam 1" raise ValueError(msg) def _run2_sequence_from_opticsfile(opticsfile: Path, use_b4: bool = False) -> Path: filename = "lhc_as-built.seq" if not use_b4 else "lhcb4_as-built.seq" seqfile_path = opticsfile.parent.parent / filename if not seqfile_path.is_file(): logger.error(f"Could not find sequence file '{filename}' at expected location '{seqfile_path}'") msg = f"No sequence file found at '{seqfile_path}'" raise ValueError(msg) return seqfile_path logger.debug("Creating Run 2 setup MAD-X instance") echo, warn = kwargs.pop("echo", False), kwargs.pop("warn", False) madx = Madx(**kwargs) madx.option(echo=echo, warn=warn) logger.debug("Calling sequence") madx.call(_fullpath(_run2_sequence_from_opticsfile(Path(opticsfile)))) make_lhc_beams(madx, energy=energy, b4=use_b4, nemitt_x=3.75e-6, nemitt_y=3.75e-6) if slicefactor: logger.debug("A slicefactor was provided, slicing the sequence") make_lhc_thin(madx, sequence=f"lhcb{beam:d}", slicefactor=slicefactor) make_lhc_beams(madx, energy=energy, b4=use_b4, nemitt_x=3.75e-6, nemitt_y=3.75e-6) re_cycle_sequence(madx, sequence=f"lhcb{beam:d}", start=f"MSIA.EXIT.B{beam:d}") logger.debug("Calling optics file from the 'operation/optics' folder") madx.call(opticsfile) make_lhc_beams(madx, energy=energy, b4=use_b4, nemitt_x=3.75e-6, nemitt_y=3.75e-6) madx.command.use(sequence=f"lhcb{beam:d}") return madx
[docs] def prepare_lhc_run3( opticsfile: str, beam: int = 1, use_b4: bool = False, energy: float = 6800, slicefactor: int | None = None, **kwargs ) -> Madx: """ .. versionadded:: 1.0.0 Returns a prepared default ``LHC`` setup for the given *opticsfile*, for a Run 3 setup. Both beams are made with a default Run 3 configuration, and the ``lhcb`` sequence for the given beam is re-cycled from ``MSIA.EXIT.B{beam}`` as in the ``OMC`` model_creator, and then ``USE``-d. Specific variable settings can be given as keyword arguments. Important --------- As this is a Run 3 setup, it is assumed that the ``acc-models-lhc`` repo is available in the root space, which is needed by the different files in the ``acc-models-lhc`` repo itself. Note ---- Matching is **not** performed by this function and should be taken care of by the user, but the working point should be set by the definitions in the *opticsfile*. Parameters ---------- opticsfile : str The name of the optics file to be used. Can be the string path to the file or only the opticsfile name itself, which would be looked for at the **acc-models-lhc/operation/optics/** path. beam : int The beam to set up for. Defaults to beam 1. use_b4 : bool If `True`, the lhcb4 sequence file will be used. This is the beam 2 sequence but for tracking purposes. Defaults to `False`. energy : float The beam energy to set up for, in [GeV]. Defaults to 6800. slicefactor : int, optional If provided, the sequence will be sliced and made thin. Defaults to `None`, which leads to an unsliced sequence. **kwargs If `echo` or `warn` are found in the keyword arguments they will be transmitted as options to ``MAD-X`` (by default these two are given as `False`). Any other keyword argument is transmitted to the `~cpymad.madx.Madx` creation call. Returns ------- cpymad.madx.Madx An instanciated `~cpymad.madx.Madx` object with the required configuration. Example ------- .. code-block:: python madx = prepare_lhc_run3( "R2022a_A30cmC30cmA10mL200cm.madx", slicefactor=4, stdout=True ) """ if use_b4 and beam != _BEAM_FOR_B4: logger.error("Cannot use beam 4 sequence file for beam 1") msg = "Cannot use beam 4 sequence file for beam 1" raise ValueError(msg) logger.debug("Creating Run 3 setup MAD-X instance") echo, warn = kwargs.pop("echo", False), kwargs.pop("warn", False) madx = Madx(**kwargs) madx.option(echo=echo, warn=warn) sequence = "lhc.seq" if not use_b4 else "lhcb4.seq" logger.debug(f"Calling sequence file '{sequence}'") madx.call(f"acc-models-lhc/{sequence}") make_lhc_beams(madx, energy=energy, b4=use_b4) if slicefactor: logger.debug("A slicefactor was provided, slicing the sequence") make_lhc_thin(madx, sequence=f"lhcb{beam:d}", slicefactor=slicefactor) make_lhc_beams(madx, energy=energy, b4=use_b4) re_cycle_sequence(madx, sequence=f"lhcb{beam:d}", start=f"MSIA.EXIT.B{beam:d}") logger.debug("Calling optics file from the 'operation/optics' folder") if Path(opticsfile).is_file(): madx.call(opticsfile) else: madx.call(f"acc-models-lhc/operation/optics/{Path(opticsfile).with_suffix('.madx')}") make_lhc_beams(madx, energy=energy, b4=use_b4) madx.command.use(sequence=f"lhcb{beam:d}") return madx
[docs] class LHCSetup: """ .. versionadded:: 1.0.0 context manager to prepare an LHC Run 2 or Run 3 setup: calling sequence and opticsfile, re-cycling as is done in the ``OMC`` model creator, making beams, potentially slicing, etc. For details on the achieved setups, look at the `~prepare_lhc_run2` or `~prepare_lhc_run3` functions. Important --------- For the Run 3 setup, it is assumed that the **acc-models-lhc** repo is available in the root space. Note ---- Matching is **not** performed by this function and should be taken care of by the user, but the working point should be set by the definitions in the *opticsfile*. Note ---- To do tracking for beam 2, remember that the ``lhcb4`` sequence needs to be called. This is handled by giving the ``use_b4`` argument as `True` to the constructor. Parameters ---------- run : int Which run to set up for, should be 2 or 3. Defaults to run 3. opticsfile : str The name of the optics file to be used. For a Run 2 setup, should be the string path to the file. For a Run 3 setup, can be the string path to the file or only the opticsfile name itself, which would be looked for at the **acc-models-lhc/operation/optics/** path. Defaults to `None`, which will raise an error. beam : int The beam to set up for. Defaults to beam 1. use_b4 : bool If `True`, the lhcb4 sequence file will be used. This is the beam 2 sequence but for tracking purposes. Defaults to `False`. energy : float The beam energy to set up for, in [GeV]. Defaults to 6800, to match the default of Run 3. slicefactor : int, optional If provided, the sequence will be sliced and made thin. Defaults to `None`, which leads to an unsliced sequence. **kwargs If `echo` or `warn` are found in the keyword arguments they will be transmitted as options to ``MAD-X`` (by default these two are given as `False`). Any other keyword argument is transmitted to the `~cpymad.madx.Madx` creation call. Returns ------- cpymad.madx.Madx An instanciated context manager `~cpymad.madx.Madx` object with the required configuration. Raises ------ NotImplementedError If the *run* argument is not 2 or 3. AssertionError If the *opticsfile* argument is not provided. Examples -------- Get a Run 2 setup for beam 2: .. code-block:: python with LHCSetup(run=2, opticsfile="2018/PROTON/opticsfile.22", beam=2) as madx: pass # do some stuff Get a Run 3 setup for beam 1, with a sliced sequence and muted output: .. code-block:: python with LHCSetup( run=3, opticsfile="R2022a_A30cmC30cmA10mL200cm.madx", slicefactor=4, stdout=False, ) as madx: pass # do some stuff """ def __init__( self, run: int = 3, opticsfile: str | None = None, beam: int = 1, use_b4: bool = False, energy: float = 6800, slicefactor: int | None = None, **kwargs, ): if opticsfile is None: # don't want to move arg and mess users code msg = "An opticsfile must be provided" raise ValueError(msg) if use_b4 and beam != _BEAM_FOR_B4: logger.error("Cannot use beam 4 sequence file for beam 1") msg = "Cannot use beam 4 sequence file for beam 1" raise ValueError(msg) if int(run) not in (2, 3): msg = "This setup is only possible for Run 2 and Run 3 configurations." raise NotImplementedError(msg) if run == _RUN2: self.madx = prepare_lhc_run2( opticsfile=opticsfile, beam=beam, use_b4=use_b4, energy=energy, slicefactor=slicefactor, **kwargs ) else: self.madx = prepare_lhc_run3( opticsfile=opticsfile, beam=beam, use_b4=use_b4, energy=energy, slicefactor=slicefactor, **kwargs ) def __enter__(self): return self.madx def __exit__(self, *exc_info): self.madx.quit()
# ----- Configuration Utlites ----- #
[docs] def make_lhc_beams( madx: Madx, /, energy: float = 7000, nemitt_x: float = 2.5e-6, nemitt_y: float = 2.5e-6, b4: bool = False, **kwargs, ) -> None: """ .. versionadded:: 0.15.0 Defines beams with default configuratons for ``LHCB1`` and ``LHCB2`` sequences. Parameters ---------- madx : cpymad.madx.Madx An instanciated `~cpymad.madx.Madx` object. Positional only. energy : float Beam energy, in [GeV]. Defaults to 7000. nemitt_x : float Normalized horizontal emittance in [m]. Will be used to calculate geometric emittance which is then fed to the ``BEAM`` command. Defaults to the Run 3 value of 2.5e-6m. nemitt_y : float Normalized vertical emittance in [m]. Will be used to calculate geometric emittance which is then fed to the ``BEAM`` command. Defaults to the Run 3 value of 2.5e-6m. b4 : bool If `True`, will consider one is using ``lhb4`` to do tracking on beam 2, and will properly set the ``bv`` flag to 1. Defaults to `False`. **kwargs Any other keyword argument is given to the ``MAD-X`` ``BEAM`` command. Examples -------- .. code-block:: python make_lhc_beams(madx, energy=6800, nemitt_x=2.75e-6, nemitt_y=3e-6) Setting up in a way compatible for tracking of beam 2 (needs to call ``lhcb4`` and set ``bv`` to 1): .. code-block:: python make_lhc_beams(madx, energy=6800, nemitt_x=3e-6, nemitt_y=3e-6, b4=True) """ logger.debug("Making default beams for 'lhcb1' and 'lhbc2' sequences") madx.globals["NRJ"] = energy madx.globals["brho"] = energy * 1e9 / madx.globals.clight geometric_emit_x = madx.globals["geometric_emit_x"] = nemitt_x / (energy / 0.938) geometric_emit_y = madx.globals["geometric_emit_y"] = nemitt_y / (energy / 0.938) n_part = kwargs.pop("npart", 1.15e11) # keep default, let user override sigma_e = kwargs.pop("sige", 4.5e-4) # keep default, let user override for beam in (1, 2): bv = 1 if beam == 1 or b4 is True else -1 logger.debug(f"Defining beam for sequence 'lhcb{beam:d}'") madx.command.beam( sequence=f"lhcb{beam:d}", particle="proton", bv=bv, energy=energy, npart=n_part, ex=geometric_emit_x, ey=geometric_emit_y, sige=sigma_e, **kwargs, )
[docs] def make_lhc_thin(madx: Madx, /, sequence: str, slicefactor: int = 1, **kwargs) -> None: """ .. versionadded:: 0.15.0 Executes the ``MAKETHIN`` command for the LHC sequence as previously done in ``MAD-X`` macros. This will use the ``teapot`` style and will enforce ``makedipedge``. One can find an example use of this function in the :ref:`AC Dipole Tracking <demo-ac-dipole-tracking>` and :ref:`Free Tracking <demo-free-tracking>` example galleries. Parameters ---------- madx : cpymad.madx.Madx An instanciated `~cpymad.madx.Madx` object. Positional only. sequence : str The sequence to use for the ``MAKETHIN`` command. slicefactor : int The slice factor to apply in ``MAKETHIN``, which is a factor applied to default values for different elements, as did the old macro. Defaults to 1. **kwargs Any keyword argument will be transmitted to the ``MAD-X`` ``MAKETHN`` command, namely ``style`` (will default to ``teapot``) and the ``makedipedge`` flag (will default to `True`). Example ------- .. code-block:: python make_lhc_thin(madx, sequence="lhcb1", slicefactor=4) """ logger.debug(f"Slicing sequence '{sequence}'") madx.select(flag="makethin", clear=True) four_slices_patterns = [r"mbx\.", r"mbrb\.", r"mbrc\.", r"mbrs\."] four_slicefactor_patterns = [ r"mqwa\.", r"mqwb\.", r"mqy\.", r"mqm\.", r"mqmc\.", r"mqml\.", r"mqtlh\.", r"mqtli\.", r"mqt\.", ] logger.debug("Defining slices for general MB and MQ elements") madx.select(flag="makethin", class_="MB", slice_=2) madx.select(flag="makethin", class_="MQ", slice_=2 * slicefactor) logger.debug("Defining slices for triplets") madx.select(flag="makethin", class_="mqxa", slice_=16 * slicefactor) madx.select(flag="makethin", class_="mqxb", slice_=16 * slicefactor) logger.debug("Defining slices for various specifc mb elements") for pattern in four_slices_patterns: madx.select(flag="makethin", pattern=pattern, slice_=4) logger.debug("Defining slices for varous specifc mq elements") for pattern in four_slicefactor_patterns: madx.select(flag="makethin", pattern=pattern, slice_=4 * slicefactor) madx.use(sequence=sequence) style = kwargs.get("style", "teapot") makedipedge = kwargs.get("makedipedge", False) # defaults to False to compensate default TEAPOT style madx.command.makethin(sequence=sequence, style=style, makedipedge=makedipedge)
[docs] def re_cycle_sequence(madx: Madx, /, sequence: str = "lhcb1", start: str = "IP3") -> None: """ .. versionadded:: 0.15.0 Re-cycles the provided *sequence* from a different starting point, given as *start*. One can find an exemple use of this function in the :ref:`AC Dipole Tracking <demo-ac-dipole-tracking>` and :ref:`Free Tracking <demo-free-tracking>` example galleries. Parameters ---------- madx : cpymad.madx.Madx An instanciated `~cpymad.madx.Madx` object. Positional only. sequence : str The sequence to re-cycle. Defaults to "lhcb1". start : str The element to start the new cycle from. Defaults to "IP3". Example ------- .. code-block:: python re_cycle_sequence(madx, sequence="lhcb1", start="MSIA.EXIT.B1") """ logger.debug(f"Re-cycling sequence '{sequence}' from {start}") madx.command.seqedit(sequence=sequence) madx.command.flatten() madx.command.cycle(start=start) madx.command.endedit()
[docs] def lhc_orbit_variables() -> tuple[list[str], dict[str, str]]: """ .. versionadded:: 0.8.0 Get the variable names used for orbit setup in the (HL)LHC. Initial implementation credits go to :user:`Joschua Dilly <joschd>`. Returns ------- tuple[list[str], dict[str, str]] A `tuple` with a `list` of all orbit variables, and a `dict` of additional variables, that in the default configurations have the same value as another variable. Example ------- .. code-block:: python variables, specials = lhc_orbit_variables() """ logger.trace("Returning (HL)LHC orbit variables") on_variables = ( "crab1", "crab5", # exists only in HL-LHC "x1", "sep1", "o1", "oh1", "ov1", "x2", "sep2", "o2", "oe2", "a2", "oh2", "ov2", "x5", "sep5", "o5", "oh5", "ov5", "x8", "sep8", "o8", "a8", "sep8h", "x8v", "oh8", "ov8", "alice", "sol_alice", "lhcb", "sol_atlas", "sol_cms", ) variables = [f"on_{var}" for var in on_variables] + [f"phi_IR{ir:d}" for ir in (1, 2, 5, 8)] special = { "on_ssep1": "on_sep1", "on_xx1": "on_x1", "on_ssep5": "on_sep5", "on_xx5": "on_x5", } return variables, special
[docs] def setup_lhc_orbit(madx: Madx, /, scheme: str = "flat", **kwargs) -> dict[str, float]: """ .. versionadded:: 0.8.0 Automated orbit setup for (HL)LHC runs, for some default schemes. It is assumed that at least sequence and optics files have been called. Initial implementation credits go to :user:`Joschua Dilly <joschd>`. Parameters ---------- madx : cpymad.madx.Madx An instanciated `~cpymad.madx.Madx` object. Positional only. scheme : str The default scheme to apply, as defined in the `LHC_CROSSING_SCHEMES` constant. Accepted values are keys of `LHC_CROSSING_SCHEMES`. Defaults to "flat" (every orbit variable to 0). **kwargs Any standard crossing scheme variables (`on_x1`, `phi_IR1`, etc). Values given here override the values in the default scheme configurations. Returns ------- dict[str, float] A `dict` of all orbit variables set, and their values as set in the ``MAD-X`` globals. Example ------- .. code-block:: python orbit_setup = setup_lhc_orbit(madx, scheme="lhc_top") """ if scheme not in LHC_CROSSING_SCHEMES: logger.error(f"Invalid scheme parameter, should be one of {LHC_CROSSING_SCHEMES.keys()}") msg = "Invalid scheme parameter given" raise ValueError(msg) logger.debug("Getting orbit variables") variables, special = lhc_orbit_variables() scheme_dict = LHC_CROSSING_SCHEMES[scheme] final_scheme = {} for orbit_variable in variables: variable_value = kwargs.get(orbit_variable, scheme_dict.get(orbit_variable, 0)) logger.trace(f"Setting orbit variable '{orbit_variable}' to {variable_value}") # Sets value in MAD-X globals & returned dict, taken from scheme dict or kwargs if provided madx.globals[orbit_variable] = final_scheme[orbit_variable] = variable_value for special_variable, copy_from in special.items(): special_variable_value = kwargs.get(special_variable, madx.globals[copy_from]) logger.trace(f"Setting special orbit variable '{special_variable}' to {special_variable_value}") # Sets value in MAD-X globals & returned dict, taken from a given global or kwargs if provided madx.globals[special_variable] = final_scheme[special_variable] = special_variable_value return final_scheme
# ----- Helpers ----- # def _fullpath(filepath: Path) -> str: """ .. versionadded:: 1.0.0 Returns the full string path to the provided *filepath*. """ return str(filepath.absolute())