diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 10b1829cc..b0bed9ad3 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -30,23 +30,29 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + name: Install Python + with: + python-version: '3.x' + - name: Install Packages run: | sudo apt-get update sudo apt-get install --yes cmake openmpi-bin libfftw3-dev libfftw3-mpi-dev libopenmpi-dev libhdf5-openmpi-dev python -m pip install --upgrade pip + python -m pip install --upgrade pipx python -m pip install --upgrade wheel - python -m pip install --upgrade cmake - export CMAKE="$HOME/.local/bin/cmake" && echo "CMAKE=$CMAKE" >> $GITHUB_ENV python -m pip install --upgrade numpy python -m pip install --upgrade mpi4py python -m pip install --upgrade pytest + python -m pip install --upgrade cmake + python -m pipx install cmake - name: Configure (C++) if: ${{ matrix.language == 'cpp' }} run: | - $CMAKE -S . -B build -DImpactX_FFT=ON -DImpactX_PYTHON=OFF + cmake -S . -B build -DImpactX_FFT=ON -DImpactX_PYTHON=OFF - name: Initialize CodeQL uses: github/codeql-action/init@v3 @@ -64,7 +70,7 @@ jobs: - name: Build (C++) if: ${{ matrix.language == 'cpp' }} run: | - $CMAKE --build build -j 4 + cmake --build build -j 4 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index e0c6efdc9..ec380186d 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -16,12 +16,15 @@ jobs: HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: TRUE steps: - uses: actions/checkout@v4 - - name: install dependencies + - uses: actions/setup-python@v5 + name: Install Python + with: + python-version: '3.x' + - name: install brew dependencies run: | set +e brew unlink gcc brew update - brew install --overwrite python brew install adios2 brew install ccache brew install cmake @@ -32,19 +35,15 @@ jobs: brew install ninja brew install open-mpi brew install pkg-config - - python3 -m pip install --upgrade pip - python3 -m pip install --upgrade virtualenv - - python3 -m venv py-venv - source py-venv/bin/activate + set -e + - name: install pip dependencies + run: | python3 -m pip install --upgrade pip python3 -m pip install --upgrade build packaging setuptools wheel pytest python3 -m pip install --upgrade -r requirements_mpi.txt python3 -m pip install --upgrade -r src/python/impactx/dashboard/requirements.txt python3 -m pip install --upgrade -r examples/requirements.txt python3 -m pip install --upgrade -r tests/python/requirements.txt - set -e python3 -m pip install --upgrade pipx python3 -m pipx install openPMD-validator - name: CCache Cache @@ -65,13 +64,10 @@ jobs: export CCACHE_SLOPPINESS=time_macros ccache -z - source py-venv/bin/activate - cmake -S . -B build \ -DCMAKE_VERBOSE_MAKEFILE=ON \ -DImpactX_FFT=ON \ - -DImpactX_PYTHON=ON \ - -DPython_EXECUTABLE=$(which python3) + -DImpactX_PYTHON=ON cmake --build build -j 3 du -hs ~/Library/Caches/ccache @@ -79,15 +75,12 @@ jobs: - name: run tests run: | - source py-venv/bin/activate - ctest --test-dir build --output-on-failure -E pytest.AMReX - name: run installed python module run: | cmake --build build --target pip_install - source py-venv/bin/activate python3 examples/fodo/run_fodo.py - name: run installed app diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f78e3be0a..1d91577fd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: trailing-whitespace args: [--markdown-linebreak-ext=md] @@ -66,7 +66,7 @@ repos: # Python: Ruff linter & formatter # https://docs.astral.sh/ruff/ - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.3 + rev: v0.6.9 hooks: # Run the linter - id: ruff diff --git a/CMakeLists.txt b/CMakeLists.txt index 8cadda4eb..2ad0166d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ # Preamble #################################################################### # cmake_minimum_required(VERSION 3.24.0) -project(ImpactX VERSION 24.08) +project(ImpactX VERSION 24.09) include(${ImpactX_SOURCE_DIR}/cmake/ImpactXFunctions.cmake) @@ -394,9 +394,9 @@ install(CODE "file(CREATE_LINK # if(ImpactX_PYTHON) set(PY_PIP_OPTIONS "-v" CACHE STRING - "Additional parameters to pass to `pip`") + "Additional parameters to pass to `pip` as ; separated list") set(PY_PIP_INSTALL_OPTIONS "" CACHE STRING - "Additional parameters to pass to `pip install`") + "Additional parameters to pass to `pip install` as ; separated list") # add a prefix to custom targets so we do not collide if used as a subproject if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) @@ -412,7 +412,8 @@ if(ImpactX_PYTHON) ${CMAKE_COMMAND} -E rm -f -r impactx-whl COMMAND ${CMAKE_COMMAND} -E env PYIMPACTX_LIBDIR=$ - ${Python_EXECUTABLE} -m pip ${PY_PIP_OPTIONS} wheel --no-build-isolation --no-deps --wheel-dir=impactx-whl ${ImpactX_SOURCE_DIR} + ${Python_EXECUTABLE} -m pip ${PY_PIP_OPTIONS} wheel --no-build-isolation --no-deps --wheel-dir=impactx-whl "${ImpactX_SOURCE_DIR}" + COMMAND_EXPAND_LISTS VERBATIM WORKING_DIRECTORY ${ImpactX_BINARY_DIR} DEPENDS @@ -427,6 +428,7 @@ if(ImpactX_PYTHON) endif() add_custom_target(${ImpactX_CUSTOM_TARGET_PREFIX}pip_install_requirements ${Python_EXECUTABLE} -m pip ${PY_PIP_OPTIONS} install ${PY_PIP_INSTALL_OPTIONS} -r "${ImpactX_SOURCE_DIR}/${pyImpactX_REQUIREMENT_FILE}" + COMMAND_EXPAND_LISTS VERBATIM WORKING_DIRECTORY ${ImpactX_BINARY_DIR} ) @@ -444,6 +446,7 @@ if(ImpactX_PYTHON) add_custom_target(${ImpactX_CUSTOM_TARGET_PREFIX}pip_install ${CMAKE_COMMAND} -E env IMPACTX_MPI=${ImpactX_MPI} ${Python_EXECUTABLE} -m pip ${PY_PIP_OPTIONS} install --force-reinstall --no-index --no-deps ${PY_PIP_INSTALL_OPTIONS} --find-links=impactx-whl impactx + COMMAND_EXPAND_LISTS VERBATIM WORKING_DIRECTORY ${ImpactX_BINARY_DIR} DEPENDS @@ -456,6 +459,7 @@ if(ImpactX_PYTHON) add_custom_target(${ImpactX_CUSTOM_TARGET_PREFIX}pip_install_nodeps ${CMAKE_COMMAND} -E env IMPACTX_MPI=${ImpactX_MPI} ${Python_EXECUTABLE} -m pip ${PY_PIP_OPTIONS} install --force-reinstall --no-index --no-deps ${PY_PIP_INSTALL_OPTIONS} --find-links=impactx-whl impactx + COMMAND_EXPAND_LISTS VERBATIM WORKING_DIRECTORY ${ImpactX_BINARY_DIR} DEPENDS diff --git a/cmake/dependencies/ABLASTR.cmake b/cmake/dependencies/ABLASTR.cmake index 7e900e00a..7eeb0504d 100644 --- a/cmake/dependencies/ABLASTR.cmake +++ b/cmake/dependencies/ABLASTR.cmake @@ -127,7 +127,7 @@ macro(find_ablastr) set(COMPONENT_DIM 3D) set(COMPONENT_PRECISION ${ImpactX_PRECISION} P${ImpactX_PRECISION}) - find_package(ABLASTR 24.08 CONFIG REQUIRED COMPONENTS ${COMPONENT_DIM}) + find_package(ABLASTR 24.09 CONFIG REQUIRED COMPONENTS ${COMPONENT_DIM}) message(STATUS "ABLASTR: Found version '${ABLASTR_VERSION}'") endif() @@ -161,7 +161,7 @@ set(ImpactX_openpmd_src "" set(ImpactX_ablastr_repo "https://github.com/ECP-WarpX/WarpX.git" CACHE STRING "Repository URI to pull and build ABLASTR from if(ImpactX_ablastr_internal)") -set(ImpactX_ablastr_branch "0838941a6693df711769a90a8a989c9a920b0abe" +set(ImpactX_ablastr_branch "d1a338e90ed1ad7ac2f010f47409aa48a2265c88" CACHE STRING "Repository branch for ImpactX_ablastr_repo if(ImpactX_ablastr_internal)") diff --git a/cmake/dependencies/pyAMReX.cmake b/cmake/dependencies/pyAMReX.cmake index fbaeb5a8d..4158e63a3 100644 --- a/cmake/dependencies/pyAMReX.cmake +++ b/cmake/dependencies/pyAMReX.cmake @@ -59,7 +59,7 @@ function(find_pyamrex) endif() elseif(NOT ImpactX_pyamrex_internal) # TODO: MPI control - find_package(pyAMReX 24.08 CONFIG REQUIRED) + find_package(pyAMReX 24.09 CONFIG REQUIRED) message(STATUS "pyAMReX: Found version '${pyAMReX_VERSION}'") endif() endfunction() @@ -74,7 +74,7 @@ option(ImpactX_pyamrex_internal "Download & build pyAMReX" ON) set(ImpactX_pyamrex_repo "https://github.com/AMReX-Codes/pyamrex.git" CACHE STRING "Repository URI to pull and build pyamrex from if(ImpactX_pyamrex_internal)") -set(ImpactX_pyamrex_branch "abdf332e25bfeef2b4d613d7adbe93fb8cf3e2f7" +set(ImpactX_pyamrex_branch "1c66690f83244196c5655293f1381303a7d1589d" CACHE STRING "Repository branch for ImpactX_pyamrex_repo if(ImpactX_pyamrex_internal)") diff --git a/docs/source/conf.py b/docs/source/conf.py index bdb3f8639..e553e8d4d 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -73,9 +73,9 @@ # built documents. # # The short X.Y version. -version = "24.08" +version = "24.09" # The full version, including alpha/beta/rc tags. -release = "24.08" +release = "24.09" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/source/dataanalysis/dataanalysis.rst b/docs/source/dataanalysis/dataanalysis.rst index 4371f317c..cc170b57a 100644 --- a/docs/source/dataanalysis/dataanalysis.rst +++ b/docs/source/dataanalysis/dataanalysis.rst @@ -84,7 +84,7 @@ The code writes out the values in an ASCII file prefixed ``reduced_beam_characte * ``sig_px``, ``sig_py``, ``sig_pt`` Standard deviation of the particle momentum deviations (energy difference for ``pt``) normalized by the magnitude of the reference particle momentum (unit: dimensionless) * ``emittance_x``, ``emittance_y``, ``emittance_t`` - Normalized rms beam emittance (unit: meter) + Unnormalized rms beam emittances (unit: meter) * ``alpha_x``, ``alpha_y``, ``alpha_t`` Courant-Snyder (Twiss) alpha (unit: dimensionless). Transverse Twiss functions are calculated after removing correlations with particle energy. * ``beta_x``, ``beta_y``, ``beta_t`` @@ -93,6 +93,11 @@ The code writes out the values in an ASCII file prefixed ``reduced_beam_characte Horizontal and vertical dispersion (unit: meter) * ``dispersion_px``, ``dispersion_py`` Derivative of horizontal and vertical dispersion (unit: dimensionless) +* ``emittance_xn``, ``emittance_yn``, ``emittance_tn`` + Normalized rms beam emittances (unit: meter) +* ``emittance_1``, ``emittance_2``, ``emittance_3`` + Normalized rms beam eigenemittances (aka mode emittances) (unit: meter) + These three diagnostics are written optionally if the flag eigenemittances = True. * ``charge`` Total beam charge (unit: Coulomb) diff --git a/docs/source/install/cmake.rst b/docs/source/install/cmake.rst index 193fcbf2b..8fe5a85c7 100644 --- a/docs/source/install/cmake.rst +++ b/docs/source/install/cmake.rst @@ -112,8 +112,8 @@ CMake Option Default & Values Des ``ImpactX_PRECISION`` SINGLE/**DOUBLE** Floating point precision (single/double) ``ImpactX_PYTHON`` ON/**OFF** Python bindings ``Python_EXECUTABLE`` (newest found) Path to Python executable -``PY_PIP_OPTIONS`` ``-v`` Additional options for ``pip``, e.g., ``-vvv`` -``PY_PIP_INSTALL_OPTIONS`` Additional options for ``pip install``, e.g., ``--user`` +``PY_PIP_OPTIONS`` ``-v`` Additional options for ``pip``, e.g., ``-vvv;-q`` +``PY_PIP_INSTALL_OPTIONS`` Additional options for ``pip install``, e.g., ``--user;-q`` =============================== ============================================ =========================================================== ImpactX can be configured in further detail with options from AMReX, which are `documented in the AMReX manual `_. diff --git a/docs/source/install/hpc/lumi-csc/lumi_cpu_impactx.profile.example b/docs/source/install/hpc/lumi-csc/lumi_cpu_impactx.profile.example index 63b6f43d1..9eb779980 100644 --- a/docs/source/install/hpc/lumi-csc/lumi_cpu_impactx.profile.example +++ b/docs/source/install/hpc/lumi-csc/lumi_cpu_impactx.profile.example @@ -2,10 +2,10 @@ #export proj="project_..." # required dependencies -module load LUMI/23.09 partition/C +module load LUMI/24.03 partition/C module load PrgEnv-aocc -module load buildtools/23.09 -module load cray-fftw/3.3.10.5 +module load buildtools/24.03 +module load cray-fftw/3.3.10.7 # optional: just an additional text editor module load nano @@ -19,7 +19,7 @@ export PATH=${SW_DIR}/adios2-2.8.3/bin:${PATH} export PATH=${SW_DIR}/hdf5-1.14.1.2/bin:${PATH} # optional: for Python bindings or libEnsemble -module load cray-python/3.10.10 +module load cray-python/3.11.7 if [ -d "${SW_DIR}/venvs/impactx-cpu-lumi" ] then diff --git a/docs/source/install/hpc/lumi-csc/lumi_gpu_impactx.profile.example b/docs/source/install/hpc/lumi-csc/lumi_gpu_impactx.profile.example index 8d6958bdb..c060f7d6c 100644 --- a/docs/source/install/hpc/lumi-csc/lumi_gpu_impactx.profile.example +++ b/docs/source/install/hpc/lumi-csc/lumi_gpu_impactx.profile.example @@ -2,9 +2,9 @@ #export proj="project_..." # required dependencies -module load LUMI/23.09 partition/G -module load rocm/5.2.3 -module load buildtools/23.09 +module load LUMI/24.03 partition/G +module load rocm/6.0.3 +module load buildtools/24.03 # optional: just an additional text editor module load nano @@ -18,7 +18,7 @@ export PATH=${SW_DIR}/adios2-2.8.3/bin:${PATH} export PATH=${SW_DIR}/hdf5-1.14.1.2/bin:${PATH} # optional: for Python bindings or libEnsemble -module load cray-python/3.10.10 +module load cray-python/3.11.7 if [ -d "${SW_DIR}/venvs/impactx-gpu-lumi" ] then diff --git a/docs/source/usage/examples.rst b/docs/source/usage/examples.rst index 426881b7f..0658cb278 100644 --- a/docs/source/usage/examples.rst +++ b/docs/source/usage/examples.rst @@ -33,6 +33,8 @@ Single Particle Dynamics examples/achromatic_spectrometer/README.rst examples/fodo_programmable/README.rst examples/dogleg/README.rst + examples/coupled_optics/README.rst + Collective Effects ------------------ @@ -70,6 +72,16 @@ Beam Distributions examples/distgen/README + +Channels & Rings +---------------- + +.. toctree:: + :maxdepth: 1 + + examples/fodo_channel/README.rst + + Lattice Design & Optimization ----------------------------- diff --git a/docs/source/usage/parameters.rst b/docs/source/usage/parameters.rst index fb1dfb330..1b0f2644b 100644 --- a/docs/source/usage/parameters.rst +++ b/docs/source/usage/parameters.rst @@ -43,8 +43,8 @@ Initial Beam Distributions * ``beam.muypy`` (``float``, dimensionless, default: ``0``) correlation Y-Py * ``beam.mutpt`` (``float``, dimensionless, default: ``0``) correlation T-Pt - * Courant-Snyder / Twiss parameters. - To enable input via CS / Twiss parameters, add the suffix ``_from_cs`` or ``from_twiss`` to the name of the distribution. + * Courant-Snyder (Twiss) parameters. + To enable input via Courant-Snyder (Twiss) parameters, add the suffix ``from_twiss`` to the name of the distribution. Use the following parameters to characterize it: * ``beam.alphaX`` (``float``, dimensionless, default: ``0``) CS / Twiss :math:`\alpha` for X @@ -59,21 +59,21 @@ Initial Beam Distributions The following distributions are available: - * ``waterbag`` or ``waterbag_from_cs``/``waterbag_from_twiss`` for initial Waterbag distribution. + * ``waterbag`` or ``waterbag_from_twiss`` for initial Waterbag distribution. - * ``kurth6d`` or ``kurth6d_from_cs``/``kurth6d_from_twiss`` for initial 6D Kurth distribution. + * ``kurth6d`` or ``kurth6d_from_twiss`` for initial 6D Kurth distribution. - * ``gaussian`` or ``gaussian_from_cs``/``gaussian_from_twiss`` for initial 6D Gaussian (normal) distribution. + * ``gaussian`` or ``gaussian_from_twiss`` for initial 6D Gaussian (normal) distribution. - * ``kvdist`` or ``kvdist_from_cs``/``kvdist_from_twiss`` for initial K-V distribution in the transverse plane. + * ``kvdist`` or ``kvdist_from_twiss`` for initial K-V distribution in the transverse plane. The distribution is uniform in t and Gaussian in pt. - * ``kurth4d`` or ``kurth4d_from_cs``/``kurth4d_from_twiss`` for initial 4D Kurth distribution in the transverse plane. + * ``kurth4d`` or ``kurth4d_from_twiss`` for initial 4D Kurth distribution in the transverse plane. The distribution is uniform in t and Gaussian in pt. - * ``semigaussian`` or ``semigaussian_from_cs``/``semigaussian_from_twiss`` for initial Semi-Gaussian distribution. The distribution is uniform within a cylinder in (x,y,z) and Gaussian in momenta (px,py,pt). + * ``semigaussian`` or ``semigaussian_from_twiss`` for initial Semi-Gaussian distribution. The distribution is uniform within a cylinder in (x,y,z) and Gaussian in momenta (px,py,pt). - * ``triangle`` or ``triangle_from_cs``/``triangle_from_twiss`` a triangle distribution for laser-plasma acceleration related applications. + * ``triangle`` or ``triangle_from_twiss`` a triangle distribution for laser-plasma acceleration related applications. A ramped, triangular current profile with a Gaussian energy spread (possibly correlated). The transverse distribution is a 4D waterbag. @@ -449,6 +449,11 @@ Lattice Elements openPMD `iteration encoding `__: (v)ariable based, (f)ile based, (g)roup based (default) variable based is an `experimental feature with ADIOS2 `__. + * ``.period_sample_intervals`` (``int``, default value: ``1``) + + for periodic lattice, only output every Nth period (turn). + By default, diagnostics are returned every cycle. + * ``.nonlinear_lens_invariants`` (``boolean``, default value: ``false``) Compute and output the invariants H and I within the nonlinear magnetic insert element (see: ``nonlinear_lens``). @@ -683,8 +688,8 @@ Diagnostics and output This option is ignored for the openPMD output elements (remove them from the lattice to disable). * ``diag.slice_step_diagnostics`` (``boolean``, optional, default: ``false``) - By default, diagnostics is performed at the beginning and end of the simulation. - Enabling this flag will write diagnostics every step and slice step + By default, diagnostics are computed and written at the beginning and end of the simulation. + Enabling this flag will write diagnostics at every step and slice step. * ``diag.file_min_digits`` (``integer``, optional, default: ``6``) The minimum number of digits used for the step number appended to the diagnostic file names. @@ -694,6 +699,10 @@ Diagnostics and output Diagnostics for particles lost in apertures, stored as ``diags/openPMD/particles_lost.*`` at the end of the simulation. See the ``beam_monitor`` element for backend values. +* ``diag.eigenemittances`` (``boolean``, optional, default: ``false``) + If this flag is enabled, the 3 eigenemittances of the 6D beam distribution are computed and written as diagnostics. + This flag is disabled by default to reduce computational cost. + .. _running-cpp-parameters-diagnostics-insitu: diff --git a/docs/source/usage/python.rst b/docs/source/usage/python.rst index 09a327a53..73e9c0329 100644 --- a/docs/source/usage/python.rst +++ b/docs/source/usage/python.rst @@ -156,6 +156,13 @@ Collective Effects & Overall Simulation Parameters Diagnostics for particles lost in apertures. See the ``BeamMonitor`` element for backend values. + .. py:property:: eigenemittances + + Enable (``True``) or disable (``False``) output of eigenemittances at every slice step in elements (default: ``False``). + + If this flag is enabled, the 3 eigenemittances of the 6D beam distribution are computed and written as diagnostics. + This flag is disabled by default to reduce computational cost. + .. py:method:: init_grids() Initialize AMReX blocks/grids for domain decomposition & space charge mesh. @@ -401,6 +408,18 @@ Initial Beam Distributions This module provides particle beam distributions that can be used to initialize particle beams in an :py:class:`impactx.ParticleContainer`. +.. note:: + + For additional information, consult the documentation on :ref:`theory-collective-beam-distribution-input`. + For **all** except the ``thermal`` distribution we allow input in two forms: + + 1. Phase space ellipse axis intersections (ImpactX native) + 2. Courant-Snyder (Twiss) parameters + +For the input from Twiss parameters in Python, please use the helper function ``twiss``: + +.. autofunction:: impactx.twiss + .. py:class:: impactx.distribution.Gaussian(lambdax, lambday, lambdat, lambdapx, lambdapy, lambdapt, muxpx=0.0, muypy=0.0, mutpt=0.0) A 6D Gaussian distribution. @@ -484,7 +503,7 @@ This module provides elements for the accelerator lattice. :param madx_file: file name to MAD-X file with beamline elements :param nslice: number of slices used for the application of space charge -.. py:class:: impactx.elements.CFbend(ds, rc, k, dx=0, dy=0, rotation=0, nslice=1) +.. py:class:: impactx.elements.CFbend(ds, rc, k, dx=0, dy=0, rotation=0, nslice=1, name=None) A combined function bending magnet. This is an ideal Sbend with a normal quadrupole field component. @@ -498,8 +517,9 @@ This module provides elements for the accelerator lattice. :param dy: vertical translation error in m :param rotation: rotation error in the transverse plane [degrees] :param nslice: number of slices used for the application of space charge + :param name: an optional name for the element -.. py:class:: impactx.elements.ConstF(ds, kx, ky, kt, dx=0, dy=0, rotation=0, nslice=1) +.. py:class:: impactx.elements.ConstF(ds, kx, ky, kt, dx=0, dy=0, rotation=0, nslice=1, name=None) A linear Constant Focusing element. @@ -511,6 +531,7 @@ This module provides elements for the accelerator lattice. :param dy: vertical translation error in m :param rotation: rotation error in the transverse plane [degrees] :param nslice: number of slices used for the application of space charge + :param name: an optional name for the element .. py:property:: kx @@ -524,7 +545,7 @@ This module provides elements for the accelerator lattice. focusing t strength in 1/m -.. py:class:: impactx.elements.DipEdge(psi, rc, g, K2, dx=0, dy=0, rotation=0) +.. py:class:: impactx.elements.DipEdge(psi, rc, g, K2, dx=0, dy=0, rotation=0, name=None) Edge focusing associated with bend entry or exit @@ -543,15 +564,17 @@ This module provides elements for the accelerator lattice. :param dx: horizontal translation error in m :param dy: vertical translation error in m :param rotation: rotation error in the transverse plane [degrees] + :param name: an optional name for the element -.. py:class:: impactx.elements.Drift(ds, dx=0, dy=0, rotation=0, nslice=1) +.. py:class:: impactx.elements.Drift(ds, dx=0, dy=0, rotation=0, nslice=1, name=None) A drift. :param ds: Segment length in m :param nslice: number of slices used for the application of space charge + :param name: an optional name for the element -.. py:class:: impactx.elements.ChrDrift(ds, dx=0, dy=0, rotation=0, nslice=1) +.. py:class:: impactx.elements.ChrDrift(ds, dx=0, dy=0, rotation=0, nslice=1, name=None) A drift with chromatic effects included. The Hamiltonian is expanded through second order in the transverse variables (x,px,y,py), with the exact pt @@ -562,8 +585,9 @@ This module provides elements for the accelerator lattice. :param dy: vertical translation error in m :param rotation: rotation error in the transverse plane [degrees] :param nslice: number of slices used for the application of space charge + :param name: an optional name for the element -.. py:class:: impactx.elements.ExactDrift(ds, dx=0, dy=0, rotation=0, nslice=1) +.. py:class:: impactx.elements.ExactDrift(ds, dx=0, dy=0, rotation=0, nslice=1, name=None) A drift using the exact nonlinear transfer map. @@ -572,16 +596,18 @@ This module provides elements for the accelerator lattice. :param dy: vertical translation error in m :param rotation: rotation error in the transverse plane [degrees] :param nslice: number of slices used for the application of space charge + :param name: an optional name for the element -.. py:class:: impactx.elements.Kicker(xkick, ykick, unit="dimensionless", dx=0, dy=0, rotation=0) +.. py:class:: impactx.elements.Kicker(xkick, ykick, unit="dimensionless", dx=0, dy=0, rotation=0, name=None) A thin transverse kicker. :param xkick: horizontal kick strength (dimensionless OR T-m) :param ykick: vertical kick strength (dimensionless OR T-m) :param unit: specification of units (``"dimensionless"`` in units of the magnetic rigidity of the reference particle or ``"T-m"``) + :param name: an optional name for the element -.. py:class:: impactx.elements.Multipole(multipole, K_normal, K_skew, dx=0, dy=0, rotation=0) +.. py:class:: impactx.elements.Multipole(multipole, K_normal, K_skew, dx=0, dy=0, rotation=0, name=None) A general thin multipole element. @@ -591,12 +617,13 @@ This module provides elements for the accelerator lattice. :param dx: horizontal translation error in m :param dy: vertical translation error in m :param rotation: rotation error in the transverse plane [degrees] + :param name: an optional name for the element .. py::class:: impactx.elements.Empty This element does nothing. -.. py:class:: impactx.elements.NonlinearLens(knll, cnll, dx=0, dy=0, rotation=0) +.. py:class:: impactx.elements.NonlinearLens(knll, cnll, dx=0, dy=0, rotation=0, name=None) Single short segment of the nonlinear magnetic insert element. @@ -610,8 +637,9 @@ This module provides elements for the accelerator lattice. :param dx: horizontal translation error in m :param dy: vertical translation error in m :param rotation: rotation error in the transverse plane [degrees] + :param name: an optional name for the element -.. py:class:: impactx.elements.BeamMonitor(name, backend="default", encoding="g") +.. py:class:: impactx.elements.BeamMonitor(name, backend="default", encoding="g", period_sample_intervals=1) A beam monitor, writing all beam particles at fixed ``s`` to openPMD files. @@ -628,6 +656,7 @@ This module provides elements for the accelerator lattice. :param name: name of the series :param backend: I/O backend, e.g., ``bp``, ``h5``, ``json`` :param encoding: openPMD iteration encoding: (v)ariable based, (f)ile based, (g)roup based (default) + :param period_sample_intervals: for periodic lattice, only output every Nth period (turn) .. py:property:: name @@ -655,7 +684,7 @@ This module provides elements for the accelerator lattice. Scale factor (in meters^(1/2)) of the IOTA nonlinear magnetic insert element used for computing H and I. -.. py:class:: impactx.elements.Programmable +.. py:class:: impactx.elements.Programmable(ds=0.0, nslice=1, name=None) A programmable beam optics element. @@ -663,6 +692,7 @@ This module provides elements for the accelerator lattice. :param ds: Segment length in m. :param nslice: number of slices used for the application of space charge + :param name: an optional name for the element .. py:property:: push @@ -695,7 +725,7 @@ This module provides elements for the accelerator lattice. This function is called for the reference particle as it passes through the element. The reference particle is updated *before* the beam particles are pushed. -.. py:class:: impactx.elements.Quad(ds, k, dx=0, dy=0, rotation=0, nslice=1) +.. py:class:: impactx.elements.Quad(ds, k, dx=0, dy=0, rotation=0, nslice=1, name=None) A Quadrupole magnet. @@ -708,8 +738,9 @@ This module provides elements for the accelerator lattice. :param dy: vertical translation error in m :param rotation: rotation error in the transverse plane [degrees] :param nslice: number of slices used for the application of space charge + :param name: an optional name for the element -.. py:class:: impactx.elements.ChrQuad(ds, k, unit=0, dx=0, dy=0, rotation=0, nslice=1) +.. py:class:: impactx.elements.ChrQuad(ds, k, unit=0, dx=0, dy=0, rotation=0, nslice=1, name=None) A Quadrupole magnet, with chromatic effects included. The Hamiltonian is expanded through second order in the transverse variables (x,px,y,py), with the exact pt @@ -726,6 +757,7 @@ This module provides elements for the accelerator lattice. :param dy: vertical translation error in m :param rotation: rotation error in the transverse plane [degrees] :param nslice: number of slices used for the application of space charge + :param name: an optional name for the element .. py:property:: k @@ -735,7 +767,7 @@ This module provides elements for the accelerator lattice. unit specification for quad strength -.. py:class:: impactx.elements.ChrPlasmaLens(ds, k, unit=0, dx=0, dy=0, rotation=0, nslice=1) +.. py:class:: impactx.elements.ChrPlasmaLens(ds, k, unit=0, dx=0, dy=0, rotation=0, nslice=1, name=None) An active cylindrically symmetric plasma lens, with chromatic effects included. The Hamiltonian is expanded through second order in the transverse variables @@ -750,6 +782,7 @@ This module provides elements for the accelerator lattice. :param dy: vertical translation error in m :param rotation: rotation error in the transverse plane [degrees] :param nslice: number of slices used for the application of space charge + :param name: an optional name for the element .. py:property:: k @@ -759,7 +792,7 @@ This module provides elements for the accelerator lattice. unit specification for plasma lens focusing strength -.. py:class:: impactx.elements.ChrAcc(ds, ez, bz, dx=0, dy=0, rotation=0, nslice=1) +.. py:class:: impactx.elements.ChrAcc(ds, ez, bz, dx=0, dy=0, rotation=0, nslice=1, name=None) Acceleration in a uniform field Ez, with a uniform solenoidal field Bz. @@ -775,6 +808,7 @@ This module provides elements for the accelerator lattice. :param dy: vertical translation error in m :param rotation: rotation error in the transverse plane [degrees] :param nslice: number of slices used for the application of space charge + :param name: an optional name for the element .. py:property:: ez @@ -784,7 +818,7 @@ This module provides elements for the accelerator lattice. magnetic field strength in 1/m -.. py:class:: impactx.elements.RFCavity(ds, escale, freq, phase, cos_coefficients, sin_coefficients, dx=0, dy=0, rotation=0, mapsteps=1, nslice=1) +.. py:class:: impactx.elements.RFCavity(ds, escale, freq, phase, cos_coefficients, sin_coefficients, dx=0, dy=0, rotation=0, mapsteps=1, nslice=1, name=None) A radiofrequency cavity. @@ -801,8 +835,9 @@ This module provides elements for the accelerator lattice. :param rotation: rotation error in the transverse plane [degrees] :param mapsteps: number of integration steps per slice used for map and reference particle push in applied fields :param nslice: number of slices used for the application of space charge + :param name: an optional name for the element -.. py:class:: impactx.elements.Sbend(ds, rc, dx=0, dy=0, rotation=0, nslice=1) +.. py:class:: impactx.elements.Sbend(ds, rc, dx=0, dy=0, rotation=0, nslice=1, name=None) An ideal sector bend. @@ -812,8 +847,9 @@ This module provides elements for the accelerator lattice. :param dy: vertical translation error in m :param rotation: rotation error in the transverse plane [degrees] :param nslice: number of slices used for the application of space charge + :param name: an optional name for the element -.. py:class:: impactx.elements.ExactSbend(ds, phi, B=0.0, dx=0, dy=0, rotation=0, nslice=1) +.. py:class:: impactx.elements.ExactSbend(ds, phi, B=0.0, dx=0, dy=0, rotation=0, nslice=1, name=None) An ideal sector bend using the exact nonlinear map. The model consists of a uniform bending field B_y with a hard edge. Pole faces are normal to the entry and exit velocity of the reference particle. @@ -830,6 +866,7 @@ This module provides elements for the accelerator lattice. :param dy: vertical translation error in m :param rotation: rotation error in the transverse plane [degrees] :param nslice: number of slices used for the application of space charge + :param name: an optional name for the element .. py:class:: impactx.elements.Buncher(V, k, dx=0, dy=0, rotation=0) @@ -841,7 +878,7 @@ This module provides elements for the accelerator lattice. :param dy: vertical translation error in m :param rotation: rotation error in the transverse plane [degrees] -.. py:class:: impactx.elements.ShortRF(V, freq, phase=-90.0, dx=0, dy=0, rotation=0) +.. py:class:: impactx.elements.ShortRF(V, freq, phase=-90.0, dx=0, dy=0, rotation=0, name=None) A short RF cavity element (MAD-X model). @@ -851,8 +888,9 @@ This module provides elements for the accelerator lattice. :param dx: horizontal translation error in m :param dy: vertical translation error in m :param rotation: rotation error in the transverse plane [degrees] + :param name: an optional name for the element -.. py:class:: impactx.elements.SoftSolenoid(ds, bscale, cos_coefficients, sin_coefficients, unit=0, dx=0, dy=0, rotation=0, mapsteps=1, nslice=1) +.. py:class:: impactx.elements.SoftSolenoid(ds, bscale, cos_coefficients, sin_coefficients, unit=0, dx=0, dy=0, rotation=0, mapsteps=1, nslice=1, name=None) A soft-edge solenoid. @@ -870,8 +908,9 @@ This module provides elements for the accelerator lattice. :param rotation: rotation error in the transverse plane [degrees] :param mapsteps: number of integration steps per slice used for map and reference particle push in applied fields :param nslice: number of slices used for the application of space charge + :param name: an optional name for the element -.. py:class:: impactx.elements.Sol(ds, ks, dx=0, dy=0, rotation=0, nslice=1) +.. py:class:: impactx.elements.Sol(ds, ks, dx=0, dy=0, rotation=0, nslice=1, name=None) An ideal hard-edge Solenoid magnet. @@ -881,15 +920,17 @@ This module provides elements for the accelerator lattice. :param dy: vertical translation error in m :param rotation: rotation error in the transverse plane [degrees] :param nslice: number of slices used for the application of space charge + :param name: an optional name for the element -.. py:class:: impactx.elements.PRot(phi_in, phi_out) +.. py:class:: impactx.elements.PRot(phi_in, phi_out, name=None) Exact map for a pole-face rotation in the x-z plane. :param phi_in: angle of the reference particle with respect to the longitudinal (z) axis in the original frame in degrees :param phi_out: angle of the reference particle with respect to the longitudinal (z) axis in the rotated frame in degrees + :param name: an optional name for the element -.. py:class:: impactx.elements.Aperture(xmax, ymax, shape="rectangular", dx=0, dy=0, rotation=0) +.. py:class:: impactx.elements.Aperture(xmax, ymax, shape="rectangular", dx=0, dy=0, rotation=0, name=None) A thin collimator element, applying a transverse aperture boundary. @@ -899,6 +940,7 @@ This module provides elements for the accelerator lattice. :param dx: horizontal translation error in m :param dy: vertical translation error in m :param rotation: rotation error in the transverse plane [degrees] + :param name: an optional name for the element .. py:property:: shape @@ -912,7 +954,7 @@ This module provides elements for the accelerator lattice. maximum vertical coordinate -.. py:class:: impactx.elements.SoftQuadrupole(ds, gscale, cos_coefficients, sin_coefficients, dx=0, dy=0, rotation=0, mapsteps=1, nslice=1) +.. py:class:: impactx.elements.SoftQuadrupole(ds, gscale, cos_coefficients, sin_coefficients, dx=0, dy=0, rotation=0, mapsteps=1, nslice=1, name=None) A soft-edge quadrupole. @@ -927,8 +969,9 @@ This module provides elements for the accelerator lattice. :param rotation: rotation error in the transverse plane [degrees] :param mapsteps: number of integration steps per slice used for map and reference particle push in applied fields :param nslice: number of slices used for the application of space charge + :param name: an optional name for the element -.. py:class:: impactx.elements.ThinDipole(theta, rc, dx=0, dy=0, rotation=0) +.. py:class:: impactx.elements.ThinDipole(theta, rc, dx=0, dy=0, rotation=0, name=None) A general thin dipole element. @@ -937,12 +980,13 @@ This module provides elements for the accelerator lattice. :param dx: horizontal translation error in m :param dy: vertical translation error in m :param rotation: rotation error in the transverse plane [degrees] + :param name: an optional name for the element Reference: * G. Ripken and F. Schmidt, Thin-Lens Formalism for Tracking, CERN/SL/95-12 (AP), 1995. -.. py:class:: impactx.elements.TaperedPL(k, taper, unit=0, dx=0, dy=0, rotation=0) +.. py:class:: impactx.elements.TaperedPL(k, taper, unit=0, dx=0, dy=0, rotation=0, name=None) A thin nonlinear plasma lens with transverse (horizontal) taper @@ -962,6 +1006,7 @@ This module provides elements for the accelerator lattice. :param dx: horizontal translation error in m :param dy: vertical translation error in m :param rotation: rotation error in the transverse plane [degrees] + :param name: an optional name for the element .. py:property:: k diff --git a/docs/source/usage/workflows/add_element.rst b/docs/source/usage/workflows/add_element.rst index a4efae65e..db80e21eb 100644 --- a/docs/source/usage/workflows/add_element.rst +++ b/docs/source/usage/workflows/add_element.rst @@ -53,7 +53,7 @@ To simplify the logic, we use so-called `mixin classes `__ runs, prefix these lines with ``mpiexec -n 4 ...`` or ``srun -n 4 ...``, depending on the system. + +.. tab-set:: + + .. tab-item:: Python: Script + + .. literalinclude:: run_coupled_optics.py + :language: python3 + :caption: You can copy this file from ``examples/coupled_optics/run_coupled_optics.py``. + + .. tab-item:: Executable: Input File + + .. literalinclude:: input_coupled_optics.in + :language: ini + :caption: You can copy this file from ``examples/coupled_optics/input_coupled_optics.in``. + + +Analyze +------- + +We run the following script to analyze correctness: + +.. dropdown:: Script ``analysis_coupled_optics.py`` + + .. literalinclude:: analysis_coupled_optics.py + :language: python3 + :caption: You can copy this file from ``examples/coupled_optics/analysis_coupled_optics.py``. diff --git a/examples/coupled_optics/analysis_coupled_optics.py b/examples/coupled_optics/analysis_coupled_optics.py new file mode 100755 index 000000000..04b3eb998 --- /dev/null +++ b/examples/coupled_optics/analysis_coupled_optics.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +# +# Copyright 2022-2023 ImpactX contributors +# Authors: Axel Huebl, Chad Mitchell +# License: BSD-3-Clause-LBNL +# + +import numpy as np +import openpmd_api as io +from scipy.stats import moment + + +def get_moments(beam): + """Calculate standard deviations of beam position & momenta + and emittance values + + Returns + ------- + sigx, sigy, sigt, emittance_x, emittance_y, emittance_t + """ + sigx = moment(beam["position_x"], moment=2) ** 0.5 # variance -> std dev. + sigpx = moment(beam["momentum_x"], moment=2) ** 0.5 + sigy = moment(beam["position_y"], moment=2) ** 0.5 + sigpy = moment(beam["momentum_y"], moment=2) ** 0.5 + sigt = moment(beam["position_t"], moment=2) ** 0.5 + sigpt = moment(beam["momentum_t"], moment=2) ** 0.5 + + epstrms = beam.cov(ddof=0) + emittance_x = (sigx**2 * sigpx**2 - epstrms["position_x"]["momentum_x"] ** 2) ** 0.5 + emittance_y = (sigy**2 * sigpy**2 - epstrms["position_y"]["momentum_y"] ** 2) ** 0.5 + emittance_t = (sigt**2 * sigpt**2 - epstrms["position_t"]["momentum_t"] ** 2) ** 0.5 + + return (sigx, sigy, sigt, emittance_x, emittance_y, emittance_t) + + +def get_eigenemittances(openpmd_beam): + """Return eigenemittances from an openPMD particle species + + Returns + ------- + emittance_1, emittance_2, emittance_3 + """ + emittance_1 = openpmd_beam.get_attribute("emittance_1") + emittance_2 = openpmd_beam.get_attribute("emittance_2") + emittance_3 = openpmd_beam.get_attribute("emittance_3") + + return (emittance_1, emittance_2, emittance_3) + + +# initial/final beam +series = io.Series("diags/openPMD/monitor.h5", io.Access.read_only) +last_step = list(series.iterations)[-1] +initial_beam = series.iterations[1].particles["beam"] +initial = initial_beam.to_df() +final_beam = series.iterations[last_step].particles["beam"] +final = final_beam.to_df() + +# compare number of particles +num_particles = 10000 +assert num_particles == len(initial) +assert num_particles == len(final) + +print("Initial Beam:") +sigx, sigy, sigt, emittance_x, emittance_y, emittance_t = get_moments(initial) +print(f" sigx={sigx:e} sigy={sigy:e} sigt={sigt:e}") +print( + f" emittance_x={emittance_x:e} emittance_y={emittance_y:e} emittance_t={emittance_t:e}" +) + +atol = 0.0 # ignored +rtol = 2.2 * num_particles**-0.5 # from random sampling of a smooth distribution +print(f" rtol={rtol} (ignored: atol~={atol})") + +assert np.allclose( + [sigx, sigy, sigt, emittance_x, emittance_y, emittance_t], + [ + 6.4214719960819659e-005, + 3.6603372435649773e-005, + 1.9955175623579313e-004, + 1.0198263116327677e-010, + 1.0308359092878036e-010, + 4.0035161705244885e-010, + ], + rtol=rtol, + atol=atol, +) + + +print("") +print("Final Beam:") +sigx, sigy, sigt, emittance_x, emittance_y, emittance_t = get_moments(final) +print(f" sigx={sigx:e} sigy={sigy:e} sigt={sigt:e}") +print( + f" emittance_x={emittance_x:e} emittance_y={emittance_y:e} emittance_t={emittance_t:e}" +) + +atol = 0.0 # ignored +rtol = 2.2e12 * num_particles**-0.5 # from random sampling of a smooth distribution +print(f" rtol={rtol} (ignored: atol~={atol})") + +assert np.allclose( + [sigx, sigy, sigt, emittance_x, emittance_y, emittance_t], + [ + 1.922660e-03, + 2.166654e-05, + 1.101353e-04, + 8.561046e-09, + 1.020439e-10, + 8.569865e-09, + ], + rtol=rtol, + atol=atol, +) + +print("") +print("Initial eigenemittances:") +emittance_1i, emittance_2i, emittance_3i = get_eigenemittances(initial_beam) +print( + f" emittance_1={emittance_1i:e} emittance_2={emittance_2i:e} emittance_3={emittance_3i:e}" +) + +print("") +print("Final eigenemittances:") +emittance_1f, emittance_2f, emittance_3f = get_eigenemittances(final_beam) +print( + f" emittance_1={emittance_1f:e} emittance_2={emittance_2f:e} emittance_3={emittance_3f:e}" +) + +atol = 0.0 # ignored +rtol = 3.5 * num_particles**-0.5 # from random sampling of a smooth distribution +print(f" rtol={rtol} (ignored: atol~={atol})") + +assert np.allclose( + [emittance_1f, emittance_2f, emittance_3f], + [ + emittance_1i, + emittance_2i, + emittance_3i, + ], + rtol=rtol, + atol=atol, +) diff --git a/examples/coupled_optics/input_coupled_optics.in b/examples/coupled_optics/input_coupled_optics.in new file mode 100644 index 000000000..f0ee840c4 --- /dev/null +++ b/examples/coupled_optics/input_coupled_optics.in @@ -0,0 +1,72 @@ +############################################################################### +# Particle Beam(s) +############################################################################### +beam.npart = 10000 +beam.units = static +beam.kin_energy = 5.0e3 +beam.charge = 1.0e-9 +beam.particle = electron +beam.distribution = waterbag +beam.lambdaX = 2.2951017632e-5 +beam.lambdaY = 1.3084093142e-5 +beam.lambdaT = 5.5555553e-8 +beam.lambdaPx = 1.598353425e-6 +beam.lambdaPy = 2.803697378e-6 +beam.lambdaPt = 2.000000000e-6 +beam.muxpx = 0.933345606203060 +beam.muypy = 0.933345606203060 +beam.mutpt = 0.999999961419755 + + +############################################################################### +# Beamline: lattice elements and segments +############################################################################### +lattice.elements = monitor sbend1 dipedge1 sol drift1 dipedge2 sbend2 drift2 monitor +lattice.nslice = 25 + +sbend1.type = sbend +sbend1.ds = 0.500194828041958 # projected length 0.5 m, angle 2.77 deg +sbend1.rc = -10.3462283686195526 + +drift1.type = drift +drift1.ds = 5.0058489435 # projected length 5 m + +sbend2.type = sbend +sbend2.ds = 0.500194828041958 # projected length 0.5 m, angle 2.77 deg +sbend2.rc = 10.3462283686195526 + +drift2.type = drift +drift2.ds = 0.5 + +dipedge1.type = dipedge # dipole edge focusing +dipedge1.psi = -0.048345620280243 +dipedge1.rc = -10.3462283686195526 +dipedge1.g = 0.0 +dipedge1.K2 = 0.0 + +dipedge2.type = dipedge +dipedge2.psi = 0.048345620280243 +dipedge2.rc = 10.3462283686195526 +dipedge2.g = 0.0 +dipedge2.K2 = 0.0 + +sol.type = solenoid +sol.ds = 3.820395 +sol.ks = 0.8223219329893234 + +monitor.type = beam_monitor +monitor.backend = h5 + + +############################################################################### +# Algorithms +############################################################################### +algo.particle_shape = 2 +algo.space_charge = false + + +############################################################################### +# Diagnostics +############################################################################### +diag.slice_step_diagnostics = true +diag.eigenemittances = true diff --git a/examples/coupled_optics/run_coupled_optics.py b/examples/coupled_optics/run_coupled_optics.py new file mode 100644 index 000000000..c6e6632d1 --- /dev/null +++ b/examples/coupled_optics/run_coupled_optics.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +# +# Copyright 2022-2023 ImpactX contributors +# Authors: Marco Garten, Axel Huebl, Chad Mitchell +# License: BSD-3-Clause-LBNL +# +# -*- coding: utf-8 -*- + +from impactx import ImpactX, distribution, elements + +sim = ImpactX() + +# set numerical parameters and IO control +sim.particle_shape = 2 # B-spline order +sim.space_charge = False +# sim.diagnostics = False # benchmarking +sim.slice_step_diagnostics = True +sim.eigenemittances = True + +# domain decomposition & space charge mesh +sim.init_grids() + +# load a 5 GeV electron beam with an initial +# normalized transverse rms emittance of 1 um +kin_energy_MeV = 5.0e3 # reference energy +bunch_charge_C = 1.0e-9 # used with space charge +npart = 10000 # number of macro particles + +# reference particle +ref = sim.particle_container().ref_particle() +ref.set_charge_qe(-1.0).set_mass_MeV(0.510998950).set_kin_energy_MeV(kin_energy_MeV) + +# particle bunch +distr = distribution.Waterbag( + lambdaX=2.2951017632e-5, + lambdaY=1.3084093142e-5, + lambdaT=5.5555553e-8, + lambdaPx=1.598353425e-6, + lambdaPy=2.803697378e-6, + lambdaPt=2.000000000e-6, + muxpx=0.933345606203060, + muypy=0.933345606203060, + mutpt=0.999999961419755, +) +sim.add_particles(bunch_charge_C, distr, npart) + +# add beam diagnostics +monitor = elements.BeamMonitor("monitor", backend="h5") + +# design the accelerator lattice +ns = 25 # number of slices per ds in the element +rc = 10.3462283686195526 # bend radius (meters) +psi = 0.048345620280243 # pole face rotation angle (radians) +lb = 0.500194828041958 # bend arc length (meters) + +# Drift elements +dr1 = elements.Drift(ds=5.0058489435, nslice=ns) +dr2 = elements.Drift(ds=0.5, nslice=ns) + +# Bend elements +sbend1 = elements.Sbend(ds=lb, rc=-rc, nslice=ns) +sbend2 = elements.Sbend(ds=lb, rc=rc, nslice=ns) + +# Dipole Edge Focusing elements +dipedge1 = elements.DipEdge(psi=-psi, rc=-rc, g=0.0, K2=0.0) +dipedge2 = elements.DipEdge(psi=psi, rc=rc, g=0.0, K2=0.0) + +# Solenoid element +sol = elements.Sol(ds=3.820395, ks=0.8223219329893234) + +lattice_coupled = [sbend1, dipedge1, sol, dr1, dipedge2, sbend2, dr2] + +sim.lattice.append(monitor) +sim.lattice.extend(lattice_coupled) +sim.lattice.append(monitor) + +# run simulation +sim.evolve() + +# clean shutdown +sim.finalize() diff --git a/examples/cyclotron/run_cyclotron.py b/examples/cyclotron/run_cyclotron.py index d194dd8e7..63ec87e1b 100755 --- a/examples/cyclotron/run_cyclotron.py +++ b/examples/cyclotron/run_cyclotron.py @@ -49,10 +49,10 @@ ns = 1 # number of slices per ds in the element period = [ monitor, - elements.ChrAcc(ds=0.038, ez=1.12188308693e-4, bz=1.0e-14, nslice=ns), - elements.ExactSbend(ds=0.25, phi=180.0, B=1), - elements.ChrAcc(ds=0.038, ez=1.12188308693e-4, bz=1.0e-14, nslice=ns), - elements.ExactSbend(ds=0.25, phi=180.0, B=1), + elements.ChrAcc(name="acc1", ds=0.038, ez=1.12188308693e-4, bz=1.0e-14, nslice=ns), + elements.ExactSbend(name="sbend1", ds=0.25, phi=180.0, B=1), + elements.ChrAcc(name="acc2", ds=0.038, ez=1.12188308693e-4, bz=1.0e-14, nslice=ns), + elements.ExactSbend(name="sbend2", ds=0.25, phi=180.0, B=1), monitor, ] diff --git a/examples/distgen/README.rst b/examples/distgen/README.rst index 77c3ce0c9..ec23b2a3d 100644 --- a/examples/distgen/README.rst +++ b/examples/distgen/README.rst @@ -4,6 +4,17 @@ Generation of beam distributions The following examples are tests of beam initialization for distributions of various types. +.. note:: + + Please consult :ref:`theory-collective-beam-distribution-input` for our conventions on beam distribution input. + We currently offer two different methods: + + 1. Phase space ellipse axis intersections (ImpactX native) + 2. Courant-Snyder (Twiss) parameters + + For illustration purposes, some of the following examples use Twiss initialization while some use the ImpactX native initialization. + Both are equivalent. + In each example, we use a 2 GeV electron beam with initial unnormalized rms emittance of 2 nm. The matched Twiss parameters are the same as those used in the FODO example: @@ -20,10 +31,10 @@ In this test, the initial and final values of :math:`\lambda_x`, :math:`\lambda_ .. _examples-distgen-gaussian: -A 6d Gaussian distribution -============================= +A 6d Gaussian distribution from Twiss functions +=============================================== -A Gaussian distribution in all 6 phase space variables. +A Gaussian distribution in all 6 phase space variables, initialized using Courant-Snyder (Twiss) functions. In this test, the initial and final values of :math:`\lambda_x`, :math:`\lambda_y`, :math:`\lambda_t`, :math:`\epsilon_x`, :math:`\epsilon_y`, and :math:`\epsilon_t` must agree with nominal values. @@ -33,8 +44,8 @@ Run This example can be run **either** as: -* **Python** script: ``python3 run_gaussian.py`` or -* ImpactX **executable** using an input file: ``impactx input_gaussian.in`` +* **Python** script: ``python3 run_gaussian_twiss.py`` or +* ImpactX **executable** using an input file: ``impactx input_gaussian_twiss.in`` For `MPI-parallel `__ runs, prefix these lines with ``mpiexec -n 4 ...`` or ``srun -n 4 ...``, depending on the system. @@ -42,15 +53,15 @@ For `MPI-parallel `__ runs, prefix these lines with ` .. tab-item:: Python: Script - .. literalinclude:: run_gaussian.py + .. literalinclude:: run_gaussian_twiss.py :language: python3 - :caption: You can copy this file from ``examples/distgen/run_gaussian.py``. + :caption: You can copy this file from ``examples/distgen/run_gaussian_twiss.py``. .. tab-item:: Executable: Input File - .. literalinclude:: input_gaussian.in + .. literalinclude:: input_gaussian_twiss.in :language: ini - :caption: You can copy this file from ``examples/distgen/input_gaussian.in``. + :caption: You can copy this file from ``examples/distgen/input_gaussian_twiss.in``. Analyze @@ -65,62 +76,12 @@ We run the following script to analyze correctness: :caption: You can copy this file from ``examples/distgen/analysis_gaussian.py``. - -.. _examples-distgen-kvdist: - -A Kapchinskij-Vladimirskij (K-V) distribution -=============================================== - -A 4D K-V distribution in the transverse phase space variables ( + a longitudinally uniform distribution in :math:`t` + a Gaussian distribution in :math:`p_t` ). - -In this test, the initial and final values of :math:`\lambda_x`, :math:`\lambda_y`, :math:`\lambda_t`, :math:`\epsilon_x`, :math:`\epsilon_y`, and :math:`\epsilon_t` must agree with nominal values. - - -Run ---- - -This example can be run **either** as: - -* **Python** script: ``python3 run_kvdist.py`` or -* ImpactX **executable** using an input file: ``impactx input_kvdist.in`` - -For `MPI-parallel `__ runs, prefix these lines with ``mpiexec -n 4 ...`` or ``srun -n 4 ...``, depending on the system. - -.. tab-set:: - - .. tab-item:: Python: Script - - .. literalinclude:: run_kvdist.py - :language: python3 - :caption: You can copy this file from ``examples/distgen/run_kvdist.py``. - - .. tab-item:: Executable: Input File - - .. literalinclude:: input_kvdist.in - :language: ini - :caption: You can copy this file from ``examples/distgen/input_kvdist.in``. - - -Analyze -------- - -We run the following script to analyze correctness: - -.. dropdown:: Script ``analysis_kvdist.py`` - - .. literalinclude:: analysis_kvdist.py - :language: python3 - :caption: You can copy this file from ``examples/distgen/analysis_kvdist.py``. - - - - .. _examples-distgen-kvdist_from_twiss: A K-V distribution initialized from Twiss functions ====================================================== -Identical to the previous example (examples-kvdist), but initialized using Courant-Snyder Twiss functions. +This example is initialized using Courant-Snyder (Twiss) functions. In this test, the initial and final values of :math:`\lambda_x`, :math:`\lambda_y`, :math:`\lambda_t`, :math:`\epsilon_x`, :math:`\epsilon_y`, and :math:`\epsilon_t` must agree with nominal values. @@ -130,8 +91,8 @@ Run This example can be run **either** as: -* **Python** script: ``python3 run_kvdist_from_twiss.py`` or -* ImpactX **executable** using an input file: ``impactx input_kvdist_from_twiss.in`` +* **Python** script: ``python3 run_kvdist_twiss.py`` or +* ImpactX **executable** using an input file: ``impactx input_kvdist_twiss.in`` For `MPI-parallel `__ runs, prefix these lines with ``mpiexec -n 4 ...`` or ``srun -n 4 ...``, depending on the system. @@ -139,15 +100,15 @@ For `MPI-parallel `__ runs, prefix these lines with ` .. tab-item:: Python: Script - .. literalinclude:: run_kvdist_from_twiss.py + .. literalinclude:: run_kvdist_twiss.py :language: python3 - :caption: You can copy this file from ``examples/distgen/run_kvdist_from_twiss.py``. + :caption: You can copy this file from ``examples/distgen/run_kvdist_twiss.py``. .. tab-item:: Executable: Input File - .. literalinclude:: input_kvdist_from_twiss.in + .. literalinclude:: input_kvdist_twiss.in :language: ini - :caption: You can copy this file from ``examples/distgen/input_kvdist_from_twiss.in``. + :caption: You can copy this file from ``examples/distgen/input_kvdist_twiss.in``. Analyze @@ -155,11 +116,11 @@ Analyze We run the following script to analyze correctness: -.. dropdown:: Script ``analysis_kvdist_from_twiss.py`` +.. dropdown:: Script ``analysis_kvdist.py`` - .. literalinclude:: analysis_kvdist_from_twiss.py + .. literalinclude:: analysis_kvdist.py :language: python3 - :caption: You can copy this file from ``examples/distgen/analysis_kvdist_from_twiss.py``. + :caption: You can copy this file from ``examples/distgen/analysis_kvdist.py``. diff --git a/examples/distgen/input_kvdist.in b/examples/distgen/input_gaussian_twiss.in similarity index 78% rename from examples/distgen/input_kvdist.in rename to examples/distgen/input_gaussian_twiss.in index e1e551f4e..71884d95d 100644 --- a/examples/distgen/input_kvdist.in +++ b/examples/distgen/input_gaussian_twiss.in @@ -6,16 +6,16 @@ beam.units = static beam.kin_energy = 2.0e3 beam.charge = 1.0e-9 beam.particle = electron -beam.distribution = kvdist -beam.lambdaX = 3.9984884770e-5 -beam.lambdaY = 3.9984884770e-5 -beam.lambdaT = 1.0e-3 -beam.lambdaPx = 2.6623538760e-5 -beam.lambdaPy = 2.6623538760e-5 -beam.lambdaPt = 2.0e-3 -beam.muxpx = -0.846574929020762 -beam.muypy = 0.846574929020762 -beam.mutpt = 0.0 +beam.distribution = gaussian_from_twiss +beam.alphaX = -1.5905003499999992 +beam.alphaY = 1.5905003499999992 +beam.alphaT = 0.0 +beam.betaX = 2.8216194100262637 +beam.betaY = 2.8216194100262637 +beam.betaT = 0.5 +beam.emittX = 2e-09 +beam.emittY = 2e-09 +beam.emittT = 2e-06 ############################################################################### diff --git a/examples/distgen/run_gaussian_twiss.py b/examples/distgen/run_gaussian_twiss.py new file mode 100755 index 000000000..dbd4c1ce7 --- /dev/null +++ b/examples/distgen/run_gaussian_twiss.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# +# Copyright 2022-2024 ImpactX contributors +# Authors: Marco Garten, Axel Huebl, CHad Mitchell +# License: BSD-3-Clause-LBNL +# +# -*- coding: utf-8 -*- + +from impactx import ImpactX, distribution, elements, twiss + +sim = ImpactX() + +# set numerical parameters and IO control +sim.particle_shape = 2 # B-spline order +sim.space_charge = False +# sim.diagnostics = False # benchmarking +sim.slice_step_diagnostics = True + +# domain decomposition & space charge mesh +sim.init_grids() + +# load a 2 GeV electron beam with an initial +# unnormalized rms emittance of 2 nm +kin_energy_MeV = 2.0e3 # reference energy +bunch_charge_C = 1.0e-9 # used with space charge +npart = 10000 # number of macro particles + +# reference particle +ref = sim.particle_container().ref_particle() +ref.set_charge_qe(-1.0).set_mass_MeV(0.510998950).set_kin_energy_MeV(kin_energy_MeV) + +# particle bunch +distr = distribution.Gaussian( + **twiss( + beta_x=2.8216194100262637, + beta_y=2.8216194100262637, + beta_t=0.5, + emitt_x=2e-09, + emitt_y=2e-09, + emitt_t=2e-06, + alpha_x=-1.5905003499999992, + alpha_y=1.5905003499999992, + alpha_t=0.0, + ) +) +sim.add_particles(bunch_charge_C, distr, npart) + +# add beam diagnostics +monitor = elements.BeamMonitor("monitor", backend="h5") + +# design the accelerator lattice) +ns = 25 # number of slices per ds in the element +fodo = [ + monitor, + elements.Drift(name="d1", ds=0.25, nslice=ns), + monitor, + elements.Quad(name="q1", ds=1.0, k=1.0, nslice=ns), + monitor, + elements.Drift(name="d2", ds=0.5, nslice=ns), + monitor, + elements.Quad(name="q2", ds=1.0, k=-1.0, nslice=ns), + monitor, + elements.Drift(name="d3", ds=0.25, nslice=ns), + monitor, +] +# assign a fodo segment +sim.lattice.extend(fodo) + +# run simulation +sim.evolve() + +# clean shutdown +sim.finalize() diff --git a/examples/distgen/run_kurth4d.py b/examples/distgen/run_kurth4d.py new file mode 100755 index 000000000..94194dcdb --- /dev/null +++ b/examples/distgen/run_kurth4d.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +# +# Copyright 2022-2023 ImpactX contributors +# Authors: Axel Huebl, Chad Mitchell +# License: BSD-3-Clause-LBNL +# +# -*- coding: utf-8 -*- + +from impactx import ImpactX, distribution, elements + +sim = ImpactX() + +# set numerical parameters and IO control +sim.particle_shape = 2 # B-spline order +sim.space_charge = False +# sim.diagnostics = False # benchmarking +sim.slice_step_diagnostics = True + +# domain decomposition & space charge mesh +sim.init_grids() + +# load a 2 GeV proton beam with an initial +# normalized transverse rms emittance of 1 um +kin_energy_MeV = 2.0e3 # reference energy +bunch_charge_C = 1.0e-9 # used with space charge +npart = 10000 # number of macro particles + +# reference particle +ref = sim.particle_container().ref_particle() +ref.set_charge_qe(1.0).set_mass_MeV(938.27208816).set_kin_energy_MeV(kin_energy_MeV) + +# particle bunch +distr = distribution.Kurth4D( + lambdaX=1.0e-3, + lambdaY=1.0e-3, + lambdaT=1.0e-3, + lambdaPx=1.0e-3, + lambdaPy=1.0e-3, + lambdaPt=2.0e-3, + muxpx=-0.0, + muypy=0.0, + mutpt=0.0, +) + +sim.add_particles(bunch_charge_C, distr, npart) + +# add beam diagnostics +monitor = elements.BeamMonitor("monitor", backend="h5") + +# design the accelerator lattice) +constf = [ + monitor, + elements.ConstF(name="constf1", ds=2.0, kx=1.0, ky=1.0, kt=1.0e-4), + monitor, +] + +# assign a constf segment +sim.lattice.extend(constf) + +# run simulation +sim.evolve() + +# clean shutdown +sim.finalize() diff --git a/examples/distgen/run_kvdist_twiss.py b/examples/distgen/run_kvdist_twiss.py new file mode 100755 index 000000000..7d67987e5 --- /dev/null +++ b/examples/distgen/run_kvdist_twiss.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# +# Copyright 2022-2024 ImpactX contributors +# Authors: Marco Garten, Axel Huebl, CHad Mitchell +# License: BSD-3-Clause-LBNL +# +# -*- coding: utf-8 -*- + +from impactx import ImpactX, distribution, elements, twiss + +sim = ImpactX() + +# set numerical parameters and IO control +sim.particle_shape = 2 # B-spline order +sim.space_charge = False +# sim.diagnostics = False # benchmarking +sim.slice_step_diagnostics = True + +# domain decomposition & space charge mesh +sim.init_grids() + +# load a 2 GeV electron beam with an initial +# unnormalized rms emittance of 2 nm +kin_energy_MeV = 2.0e3 # reference energy +bunch_charge_C = 1.0e-9 # used with space charge +npart = 10000 # number of macro particles + +# reference particle +ref = sim.particle_container().ref_particle() +ref.set_charge_qe(-1.0).set_mass_MeV(0.510998950).set_kin_energy_MeV(kin_energy_MeV) + +# particle bunch +distr = distribution.KVdist( + **twiss( + beta_x=2.8216194100262637, + beta_y=2.8216194100262637, + beta_t=0.5, + emitt_x=2e-09, + emitt_y=2e-09, + emitt_t=2e-06, + alpha_x=-1.5905003499999992, + alpha_y=1.5905003499999992, + alpha_t=0.0, + ) +) +sim.add_particles(bunch_charge_C, distr, npart) + +# add beam diagnostics +monitor = elements.BeamMonitor("monitor", backend="h5") + +# design the accelerator lattice) +ns = 25 # number of slices per ds in the element +fodo = [ + monitor, + elements.Drift(name="d1", ds=0.25, nslice=ns), + monitor, + elements.Quad(name="q1", ds=1.0, k=1.0, nslice=ns), + monitor, + elements.Drift(name="d2", ds=0.5, nslice=ns), + monitor, + elements.Quad(name="q2", ds=1.0, k=-1.0, nslice=ns), + monitor, + elements.Drift(name="d3", ds=0.25, nslice=ns), + monitor, +] +# assign a fodo segment +sim.lattice.extend(fodo) + +# run simulation +sim.evolve() + +# clean shutdown +sim.finalize() diff --git a/examples/distgen/run_semigaussian.py b/examples/distgen/run_semigaussian.py new file mode 100755 index 000000000..b737246f4 --- /dev/null +++ b/examples/distgen/run_semigaussian.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +# +# Copyright 2022-2023 ImpactX contributors +# Authors: Axel Huebl, Chad Mitchell +# License: BSD-3-Clause-LBNL +# +# -*- coding: utf-8 -*- + +from impactx import ImpactX, distribution, elements + +sim = ImpactX() + +# set numerical parameters and IO control +sim.particle_shape = 2 # B-spline order +sim.space_charge = False +# sim.diagnostics = False # benchmarking +sim.slice_step_diagnostics = True + +# domain decomposition & space charge mesh +sim.init_grids() + +# load a 2 GeV electron beam with an initial +# unnormalized rms emittance of 2 nm +kin_energy_MeV = 2.0e3 # reference energy +bunch_charge_C = 1.0e-9 # used with space charge +npart = 10000 # number of macro particles + +# reference particle +ref = sim.particle_container().ref_particle() +ref.set_charge_qe(-1.0).set_mass_MeV(0.510998950).set_kin_energy_MeV(kin_energy_MeV) + +# particle bunch +distr = distribution.Semigaussian( + lambdaX=3.9984884770e-5, + lambdaY=3.9984884770e-5, + lambdaT=1.0e-3, + lambdaPx=2.6623538760e-5, + lambdaPy=2.6623538760e-5, + lambdaPt=2.0e-3, + muxpx=-0.846574929020762, + muypy=0.846574929020762, + mutpt=0.0, +) +sim.add_particles(bunch_charge_C, distr, npart) + +# add beam diagnostics +monitor = elements.BeamMonitor("monitor", backend="h5") + +# design the accelerator lattice) +ns = 25 # number of slices per ds in the element +fodo = [ + monitor, + elements.Drift(name="d1", ds=0.25, nslice=ns), + monitor, + elements.Quad(name="q1", ds=1.0, k=1.0, nslice=ns), + monitor, + elements.Drift(name="d2", ds=0.5, nslice=ns), + monitor, + elements.Quad(name="q2", ds=1.0, k=-1.0, nslice=ns), + monitor, + elements.Drift(name="d3", ds=0.25, nslice=ns), + monitor, +] +# assign a fodo segment +sim.lattice.extend(fodo) + +# run simulation +sim.evolve() + +# clean shutdown +sim.finalize() diff --git a/examples/dogleg/run_dogleg.py b/examples/dogleg/run_dogleg.py index 96b9cd91f..f731c76ca 100644 --- a/examples/dogleg/run_dogleg.py +++ b/examples/dogleg/run_dogleg.py @@ -53,16 +53,16 @@ lb = 0.500194828041958 # bend arc length (meters) # Drift elements -dr1 = elements.Drift(ds=5.0058489435, nslice=ns) -dr2 = elements.Drift(ds=0.5, nslice=ns) +dr1 = elements.Drift(name="dr1", ds=5.0058489435, nslice=ns) +dr2 = elements.Drift(name="dr2", ds=0.5, nslice=ns) # Bend elements -sbend1 = elements.Sbend(ds=lb, rc=-rc, nslice=ns) -sbend2 = elements.Sbend(ds=lb, rc=rc, nslice=ns) +sbend1 = elements.Sbend(name="sbend1", ds=lb, rc=-rc, nslice=ns) +sbend2 = elements.Sbend(name="sbend2", ds=lb, rc=rc, nslice=ns) # Dipole Edge Focusing elements -dipedge1 = elements.DipEdge(psi=-psi, rc=-rc, g=0.0, K2=0.0) -dipedge2 = elements.DipEdge(psi=psi, rc=rc, g=0.0, K2=0.0) +dipedge1 = elements.DipEdge(name="dipedge1", psi=-psi, rc=-rc, g=0.0, K2=0.0) +dipedge2 = elements.DipEdge(name="dipedge2", psi=psi, rc=rc, g=0.0, K2=0.0) lattice_dogleg = [sbend1, dipedge1, dr1, dipedge2, sbend2, dr2] diff --git a/examples/epac2004_benchmarks/run_bithermal.py b/examples/epac2004_benchmarks/run_bithermal.py index 0f9a0bd8b..5463cf9e8 100755 --- a/examples/epac2004_benchmarks/run_bithermal.py +++ b/examples/epac2004_benchmarks/run_bithermal.py @@ -52,6 +52,7 @@ sim.lattice.append(monitor) constf = elements.ConstF( + name="constf", ds=10.0, kx=6.283185307179586, ky=6.283185307179586, diff --git a/examples/epac2004_benchmarks/run_fodo_rf_SC.py b/examples/epac2004_benchmarks/run_fodo_rf_SC.py index 03abc6386..a86cd7d06 100755 --- a/examples/epac2004_benchmarks/run_fodo_rf_SC.py +++ b/examples/epac2004_benchmarks/run_fodo_rf_SC.py @@ -50,14 +50,15 @@ sim.lattice.append(monitor) # Quad elements -fquad = elements.Quad(ds=0.15, k=2.4669749766168163, nslice=6) -dquad = elements.Quad(ds=0.3, k=-2.4669749766168163, nslice=12) +fquad = elements.Quad(name="fquad", ds=0.15, k=2.4669749766168163, nslice=6) +dquad = elements.Quad(name="dquad", ds=0.3, k=-2.4669749766168163, nslice=12) # Drift element -dr = elements.Drift(ds=0.1, nslice=4) +dr = elements.Drift(name="dr", ds=0.1, nslice=4) # RF cavity elements gapa1 = elements.RFCavity( + name="gapa1", ds=1.0, escale=0.042631556991578, freq=7.0e8, @@ -121,6 +122,7 @@ ) gapb1 = elements.RFCavity( + name="gapb1", ds=1.0, escale=0.042631556991578, freq=7.0e8, diff --git a/examples/expanding_beam/run_expanding_fft.py b/examples/expanding_beam/run_expanding_fft.py index d27958a5c..c6b9ac53b 100755 --- a/examples/expanding_beam/run_expanding_fft.py +++ b/examples/expanding_beam/run_expanding_fft.py @@ -55,7 +55,7 @@ monitor = elements.BeamMonitor("monitor", backend="h5") # design the accelerator lattice -sim.lattice.extend([monitor, elements.Drift(ds=6.0, nslice=40), monitor]) +sim.lattice.extend([monitor, elements.Drift(name="d1", ds=6.0, nslice=40), monitor]) # run simulation sim.evolve() diff --git a/examples/expanding_beam/run_expanding_mlmg.py b/examples/expanding_beam/run_expanding_mlmg.py index c41ada0e1..42b97904b 100755 --- a/examples/expanding_beam/run_expanding_mlmg.py +++ b/examples/expanding_beam/run_expanding_mlmg.py @@ -54,7 +54,7 @@ monitor = elements.BeamMonitor("monitor", backend="h5") # design the accelerator lattice -sim.lattice.extend([monitor, elements.Drift(ds=6.0, nslice=40), monitor]) +sim.lattice.extend([monitor, elements.Drift(name="d1", ds=6.0, nslice=40), monitor]) # run simulation sim.evolve() diff --git a/examples/fodo/input_fodo_twiss.in b/examples/fodo/input_fodo_twiss.in index a94f19e4a..5e8fcd670 100644 --- a/examples/fodo/input_fodo_twiss.in +++ b/examples/fodo/input_fodo_twiss.in @@ -6,7 +6,7 @@ beam.units = static beam.kin_energy = 2.0e3 beam.charge = 1.0e-9 beam.particle = electron -beam.distribution = waterbag_from_cs +beam.distribution = waterbag_from_twiss beam.alphaX = -1.5905003499999992 beam.alphaY = 1.5905003499999992 beam.alphaT = 0.0 diff --git a/examples/fodo/run_fodo.py b/examples/fodo/run_fodo.py index 57703ed33..6a6418eca 100755 --- a/examples/fodo/run_fodo.py +++ b/examples/fodo/run_fodo.py @@ -50,15 +50,15 @@ ns = 25 # number of slices per ds in the element fodo = [ monitor, - elements.Drift(ds=0.25, nslice=ns), + elements.Drift(name="drift1", ds=0.25, nslice=ns), monitor, - elements.Quad(ds=1.0, k=1.0, nslice=ns), + elements.Quad(name="quad1", ds=1.0, k=1.0, nslice=ns), monitor, - elements.Drift(ds=0.5, nslice=ns), + elements.Drift(name="drift2", ds=0.5, nslice=ns), monitor, - elements.Quad(ds=1.0, k=-1.0, nslice=ns), + elements.Quad(name="quad2", ds=1.0, k=-1.0, nslice=ns), monitor, - elements.Drift(ds=0.25, nslice=ns), + elements.Drift(name="drift3", ds=0.25, nslice=ns), monitor, ] # assign a fodo segment diff --git a/examples/fodo/run_fodo_twiss.py b/examples/fodo/run_fodo_twiss.py new file mode 100755 index 000000000..700fdd767 --- /dev/null +++ b/examples/fodo/run_fodo_twiss.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# +# Copyright 2022-2024 ImpactX contributors +# Authors: Axel Huebl, Chad Mitchell, Marco Garten +# License: BSD-3-Clause-LBNL +# +# -*- coding: utf-8 -*- + +from impactx import ImpactX, distribution, elements, twiss + +sim = ImpactX() + +# set numerical parameters and IO control +sim.particle_shape = 2 # B-spline order +sim.space_charge = False +# sim.diagnostics = False # benchmarking +sim.slice_step_diagnostics = True + +# domain decomposition & space charge mesh +sim.init_grids() + +# load a 2 GeV electron beam with an initial +# unnormalized rms emittance of 2 nm +kin_energy_MeV = 2.0e3 # reference energy +bunch_charge_C = 1.0e-9 # used with space charge +npart = 10000 # number of macro particles + +# reference particle +ref = sim.particle_container().ref_particle() +ref.set_charge_qe(-1.0).set_mass_MeV(0.510998950).set_kin_energy_MeV(kin_energy_MeV) + +# particle bunch +distr = distribution.Waterbag( + **twiss( + beta_x=2.8216194100262637, + beta_y=2.8216194100262637, + beta_t=0.5, + emitt_x=2e-09, + emitt_y=2e-09, + emitt_t=2e-06, + alpha_x=-1.5905003499999992, + alpha_y=1.5905003499999992, + alpha_t=0.0, + ) +) +sim.add_particles(bunch_charge_C, distr, npart) + +# add beam diagnostics +monitor = elements.BeamMonitor("monitor", backend="h5") + +# design the accelerator lattice) +ns = 25 # number of slices per ds in the element +fodo = [ + monitor, + elements.Drift(name="drift1", ds=0.25, nslice=ns), + monitor, + elements.Quad(name="quad1", ds=1.0, k=1.0, nslice=ns), + monitor, + elements.Drift(name="drift2", ds=0.5, nslice=ns), + monitor, + elements.Quad(name="quad2", ds=1.0, k=-1.0, nslice=ns), + monitor, + elements.Drift(name="drift3", ds=0.25, nslice=ns), + monitor, +] +# assign a fodo segment +sim.lattice.extend(fodo) + +# run simulation +sim.evolve() + +# clean shutdown +sim.finalize() diff --git a/examples/fodo_channel/README.rst b/examples/fodo_channel/README.rst new file mode 100644 index 000000000..6282e8de0 --- /dev/null +++ b/examples/fodo_channel/README.rst @@ -0,0 +1,74 @@ +.. _examples-fodo-channel: + +FODO Channel +============ + +A 300m channel of 100 stable FODO cells (3m each) with a zero-current phase advance of 67.8 degrees. + +The matched Twiss parameters at entry are: + +* :math:`\beta_\mathrm{x} = 2.82161941` m +* :math:`\alpha_\mathrm{x} = -1.59050035` +* :math:`\beta_\mathrm{y} = 2.82161941` m +* :math:`\alpha_\mathrm{y} = 1.59050035` + +We use a 2 GeV electron beam with initial unnormalized rms emittance of 2 nm. + +The second moments of the particle distribution after the FODO cell should coincide with the second moments of the particle distribution before the FODO cell, to within the level expected due to noise due to statistical sampling. + +In this test, the initial and final values of :math:`\lambda_x`, :math:`\lambda_y`, :math:`\lambda_t`, :math:`\epsilon_x`, :math:`\epsilon_y`, and :math:`\epsilon_t` must agree with nominal values. +This test also demonstrates the ``period_sample_intervals`` capability of our beam monitor diagnostics, only creating output every 10th FODO cell + + +Run +--- + +This example can be run **either** as: + +* **Python** script: ``python3 run_fodo.py`` or +* ImpactX **executable** using an input file: ``impactx input_fodo.in`` + +For `MPI-parallel `__ runs, prefix these lines with ``mpiexec -n 4 ...`` or ``srun -n 4 ...``, depending on the system. + +.. tab-set:: + + .. tab-item:: Python: Script + + .. literalinclude:: run_fodo.py + :language: python3 + :caption: You can copy this file from ``examples/fodo/run_fodo.py``. + + .. tab-item:: Executable: Input File + + .. literalinclude:: input_fodo.in + :language: ini + :caption: You can copy this file from ``examples/fodo/input_fodo.in``. + + +Analyze +------- + +We run the following script to analyze correctness: + +.. dropdown:: Script ``analysis_fodo.py`` + + .. literalinclude:: analysis_fodo.py + :language: python3 + :caption: You can copy this file from ``examples/fodo/analysis_fodo.py``. + + +Visualize +--------- + +You can run the following script to visualize the beam evolution over time: + +.. dropdown:: Script ``plot_fodo.py`` + + .. literalinclude:: plot_fodo.py + :language: python3 + :caption: You can copy this file from ``examples/fodo/plot_fodo.py``. + +.. figure:: https://gist.githubusercontent.com/ax3l/8ae7dcb9e07c361e002fa56d6b16cb16/raw/cc952670bb946cd7a62282bc7aa3f03f3d5faa16/fodo_channel.png + :alt: preserved emittance in the FODO channel. + + FODO transverse emittance evolution (preserved) diff --git a/examples/fodo_channel/analysis_fodo.py b/examples/fodo_channel/analysis_fodo.py new file mode 100755 index 000000000..127a4af0a --- /dev/null +++ b/examples/fodo_channel/analysis_fodo.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +# +# Copyright 2022-2023 ImpactX contributors +# Authors: Axel Huebl, Chad Mitchell +# License: BSD-3-Clause-LBNL +# + + +import numpy as np +import openpmd_api as io +from scipy.stats import moment + + +def get_moments(beam): + """Calculate standard deviations of beam position & momenta + and emittance values + + Returns + ------- + sigx, sigy, sigt, emittance_x, emittance_y, emittance_t + """ + sigx = moment(beam["position_x"], moment=2) ** 0.5 # variance -> std dev. + sigpx = moment(beam["momentum_x"], moment=2) ** 0.5 + sigy = moment(beam["position_y"], moment=2) ** 0.5 + sigpy = moment(beam["momentum_y"], moment=2) ** 0.5 + sigt = moment(beam["position_t"], moment=2) ** 0.5 + sigpt = moment(beam["momentum_t"], moment=2) ** 0.5 + + epstrms = beam.cov(ddof=0) + emittance_x = (sigx**2 * sigpx**2 - epstrms["position_x"]["momentum_x"] ** 2) ** 0.5 + emittance_y = (sigy**2 * sigpy**2 - epstrms["position_y"]["momentum_y"] ** 2) ** 0.5 + emittance_t = (sigt**2 * sigpt**2 - epstrms["position_t"]["momentum_t"] ** 2) ** 0.5 + + return (sigx, sigy, sigt, emittance_x, emittance_y, emittance_t) + + +# initial/final beam +series = io.Series("diags/openPMD/monitor.h5", io.Access.read_only) +last_step = list(series.iterations)[-1] +initial = series.iterations[1].particles["beam"].to_df() +final = series.iterations[last_step].particles["beam"].to_df() + +# compare number of particles +num_particles = 10000 +assert num_particles == len(initial) +assert num_particles == len(final) + +# compare beamline length: 300m +assert np.isclose( + 300.0, series.iterations[last_step].particles["beam"].get_attribute("z_ref") +) +# compare beam monitor outputs: 10 (every 10th FODO element + 1) +assert len(series.iterations) == 11 + +print("Initial Beam:") +sigx, sigy, sigt, emittance_x, emittance_y, emittance_t = get_moments(initial) +print(f" sigx={sigx:e} sigy={sigy:e} sigt={sigt:e}") +print( + f" emittance_x={emittance_x:e} emittance_y={emittance_y:e} emittance_t={emittance_t:e}" +) + +atol = 0.0 # ignored +rtol = 2.2 * num_particles**-0.5 # from random sampling of a smooth distribution +print(f" rtol={rtol} (ignored: atol~={atol})") + +assert np.allclose( + [sigx, sigy, sigt, emittance_x, emittance_y, emittance_t], + [ + 7.5451170454175073e-005, + 7.5441588239210947e-005, + 9.9775878164077539e-004, + 1.9959540393751392e-009, + 2.0175015289132990e-009, + 2.0013820193294972e-006, + ], + rtol=rtol, + atol=atol, +) + + +print("") +print("Final Beam:") +sigx, sigy, sigt, emittance_x, emittance_y, emittance_t = get_moments(final) +print(f" sigx={sigx:e} sigy={sigy:e} sigt={sigt:e}") +print( + f" emittance_x={emittance_x:e} emittance_y={emittance_y:e} emittance_t={emittance_t:e}" +) + +atol = 0.0 # ignored +rtol = 2.2 * num_particles**-0.5 # from random sampling of a smooth distribution +print(f" rtol={rtol} (ignored: atol~={atol})") + +assert np.allclose( + [sigx, sigy, sigt, emittance_x, emittance_y, emittance_t], + [ + 7.4790118496224206e-005, + 7.5357525169680140e-005, + 9.9775879288128088e-004, + 1.9959539836392703e-009, + 2.0175014668882125e-009, + 2.0013820380883801e-006, + ], + rtol=rtol, + atol=atol, +) diff --git a/examples/distgen/input_gaussian.in b/examples/fodo_channel/input_fodo.in similarity index 60% rename from examples/distgen/input_gaussian.in rename to examples/fodo_channel/input_fodo.in index 99ccac82d..e782be7f8 100644 --- a/examples/distgen/input_gaussian.in +++ b/examples/fodo_channel/input_fodo.in @@ -6,24 +6,27 @@ beam.units = static beam.kin_energy = 2.0e3 beam.charge = 1.0e-9 beam.particle = electron -beam.distribution = gaussian -beam.lambdaX = 3.9984884770e-5 -beam.lambdaY = 3.9984884770e-5 -beam.lambdaT = 1.0e-3 -beam.lambdaPx = 2.6623538760e-5 -beam.lambdaPy = 2.6623538760e-5 -beam.lambdaPt = 2.0e-3 -beam.muxpx = -0.846574929020762 -beam.muypy = 0.846574929020762 -beam.mutpt = 0.0 +beam.distribution = waterbag_from_twiss +beam.alphaX = -1.5905003499999992 +beam.alphaY = 1.5905003499999992 +beam.alphaT = 0.0 +beam.betaX = 2.8216194100262637 +beam.betaY = 2.8216194100262637 +beam.betaT = 0.5 +beam.emittX = 2e-09 +beam.emittY = 2e-09 +beam.emittT = 2e-06 ############################################################################### # Beamline: lattice elements and segments ############################################################################### -lattice.elements = monitor drift1 quad1 drift2 quad2 drift3 monitor +lattice.elements = monitor drift1 quad1 drift2 quad2 drift3 +lattice.nslice = 5 +lattice.periods = 101 # FODO channel of 101 periods monitor.type = beam_monitor +monitor.period_sample_intervals = 10 monitor.backend = h5 drift1.type = drift @@ -49,3 +52,9 @@ drift3.ds = 0.25 ############################################################################### algo.particle_shape = 2 algo.space_charge = false + + +############################################################################### +# Diagnostics +############################################################################### +diag.slice_step_diagnostics = false diff --git a/examples/fodo_channel/plot_fodo.py b/examples/fodo_channel/plot_fodo.py new file mode 100755 index 000000000..5ec2b6715 --- /dev/null +++ b/examples/fodo_channel/plot_fodo.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +# +# Copyright 2022-2023 ImpactX contributors +# Authors: Axel Huebl, Chad Mitchell +# License: BSD-3-Clause-LBNL +# + +import argparse +import glob +import re + +import matplotlib.pyplot as plt +import openpmd_api as io +import pandas as pd +from matplotlib.ticker import MaxNLocator +from scipy.stats import moment + + +def get_moments(beam): + """Calculate standard deviations of beam position & momenta + and emittance values + + Returns + ------- + sigx, sigy, sigt, emittance_x, emittance_y, emittance_t + """ + sigx = moment(beam["position_x"], moment=2) ** 0.5 # variance -> std dev. + sigpx = moment(beam["momentum_x"], moment=2) ** 0.5 + sigy = moment(beam["position_y"], moment=2) ** 0.5 + sigpy = moment(beam["momentum_y"], moment=2) ** 0.5 + sigt = moment(beam["position_t"], moment=2) ** 0.5 + sigpt = moment(beam["momentum_t"], moment=2) ** 0.5 + + epstrms = beam.cov(ddof=0) + emittance_x = (sigx**2 * sigpx**2 - epstrms["position_x"]["momentum_x"] ** 2) ** 0.5 + emittance_y = (sigy**2 * sigpy**2 - epstrms["position_y"]["momentum_y"] ** 2) ** 0.5 + emittance_t = (sigt**2 * sigpt**2 - epstrms["position_t"]["momentum_t"] ** 2) ** 0.5 + + return (sigx, sigy, sigt, emittance_x, emittance_y, emittance_t) + + +def read_file(file_pattern): + for filename in glob.glob(file_pattern): + df = pd.read_csv(filename, delimiter=r"\s+") + if "step" not in df.columns: + step = int(re.findall(r"[0-9]+", filename)[0]) + df["step"] = step + yield df + + +def read_time_series(file_pattern): + """Read in all CSV files from each MPI rank (and potentially OpenMP + thread). Concatenate into one Pandas dataframe. + + Returns + ------- + pandas.DataFrame + """ + return pd.concat( + read_file(file_pattern), + axis=0, + ignore_index=True, + ) # .set_index('id') + + +# options to run this script +parser = argparse.ArgumentParser(description="Plot the FODO benchmark.") +parser.add_argument( + "--save-png", action="store_true", help="non-interactive run: save to PNGs" +) +args = parser.parse_args() + + +# initial/final beam +series = io.Series("diags/openPMD/monitor.h5", io.Access.read_only) +last_step = list(series.iterations)[-1] +initial = series.iterations[1].particles["beam"].to_df() +final = series.iterations[last_step].particles["beam"].to_df() +ref_particle = read_time_series("diags/ref_particle.*") + +# scaling to units +millimeter = 1.0e3 # m->mm +mrad = 1.0e3 # ImpactX uses "static units": momenta are normalized by the magnitude of the momentum of the reference particle p0: px/p0 (rad) +# mm_mrad = 1.e6 +nm_rad = 1.0e9 + + +# select a single particle by id +# particle_42 = beam[beam["id"] == 42] +# print(particle_42) + + +# steps & corresponding z +steps = list(series.iterations) + +z = list(map(lambda step: ref_particle[ref_particle["step"] == step].z.values, steps)) +# print(f"z={z}") + + +# beam transversal size & emittance over steps +moments = list( + map( + lambda step: ( + step, + get_moments(series.iterations[step].particles["beam"].to_df()), + ), + steps, + ) +) +# print(moments) +emittance_x = list(map(lambda step_val: step_val[1][3] * nm_rad, moments)) +emittance_y = list(map(lambda step_val: step_val[1][4] * nm_rad, moments)) + +# print(sigx, sigy) + + +# print beam transversal size over steps +f = plt.figure(figsize=(9, 4.8)) +ax1 = f.gca() +im_emittance_x = ax1.plot(z, emittance_x, ".-", label=r"$\epsilon_x$") +im_emittance_y = ax1.plot(z, emittance_y, ".-", label=r"$\epsilon_y$") + +ax1.legend() +ax1.set_xlabel(r"$z$ [m]") +ax1.set_ylabel(r"$\epsilon_{x,y}$ [nm]") +ax1.set_ylim([1.98, 2.03]) +ax1.xaxis.set_major_locator(MaxNLocator(integer=True)) +plt.tight_layout() +if args.save_png: + plt.savefig("fodo_lambda.png") +else: + plt.show() diff --git a/examples/fodo_channel/run_fodo.py b/examples/fodo_channel/run_fodo.py new file mode 100755 index 000000000..9eaf171f5 --- /dev/null +++ b/examples/fodo_channel/run_fodo.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +# +# Copyright 2022-2024 ImpactX contributors +# Authors: Axel Huebl, Chad Mitchell, Marco Garten +# License: BSD-3-Clause-LBNL +# +# -*- coding: utf-8 -*- + +from impactx import ImpactX, distribution, elements, twiss + +sim = ImpactX() + +# set numerical parameters and IO control +sim.particle_shape = 2 # B-spline order +sim.space_charge = False +# sim.diagnostics = False # benchmarking +sim.slice_step_diagnostics = True + +# domain decomposition & space charge mesh +sim.init_grids() + +# load a 2 GeV electron beam with an initial +# unnormalized rms emittance of 2 nm +kin_energy_MeV = 2.0e3 # reference energy +bunch_charge_C = 1.0e-9 # used with space charge +npart = 10000 # number of macro particles + +# reference particle +ref = sim.particle_container().ref_particle() +ref.set_charge_qe(-1.0).set_mass_MeV(0.510998950).set_kin_energy_MeV(kin_energy_MeV) + +# particle bunch +distr = distribution.Waterbag( + **twiss( + beta_x=2.8216194100262637, + beta_y=2.8216194100262637, + beta_t=0.5, + emitt_x=2e-09, + emitt_y=2e-09, + emitt_t=2e-06, + alpha_x=-1.5905003499999992, + alpha_y=1.5905003499999992, + alpha_t=0.0, + ) +) +sim.add_particles(bunch_charge_C, distr, npart) + +# add beam diagnostics +monitor = elements.BeamMonitor("monitor", backend="h5", period_sample_intervals=10) + +# design the accelerator lattice) +ns = 5 # number of slices per ds in the element +fodo = [ + monitor, + elements.Drift(ds=0.25, nslice=ns), + elements.Quad(ds=1.0, k=1.0, nslice=ns), + elements.Drift(ds=0.5, nslice=ns), + elements.Quad(ds=1.0, k=-1.0, nslice=ns), + elements.Drift(ds=0.25, nslice=ns), +] +# assign a fodo segment +sim.lattice.extend(fodo) + +# FODO channel of 101 periods +sim.periods = 101 + +# run simulation +sim.evolve() + +# clean shutdown +sim.finalize() diff --git a/examples/fodo_chromatic/run_fodo_chr.py b/examples/fodo_chromatic/run_fodo_chr.py index aa4677f75..1c25ff7c4 100755 --- a/examples/fodo_chromatic/run_fodo_chr.py +++ b/examples/fodo_chromatic/run_fodo_chr.py @@ -50,15 +50,15 @@ ns = 25 # number of slices per ds in the element fodo = [ monitor, - elements.ChrDrift(ds=0.25, nslice=ns), + elements.ChrDrift(name="drift1", ds=0.25, nslice=ns), monitor, - elements.ChrQuad(ds=1.0, k=1.0, nslice=ns), + elements.ChrQuad(name="quad1", ds=1.0, k=1.0, nslice=ns), monitor, - elements.ChrDrift(ds=0.5, nslice=ns), + elements.ChrDrift(name="drift2", ds=0.5, nslice=ns), monitor, - elements.ChrQuad(ds=1.0, k=-1.0, nslice=ns), + elements.ChrQuad(name="quad2", ds=1.0, k=-1.0, nslice=ns), monitor, - elements.ChrDrift(ds=0.25, nslice=ns), + elements.ChrDrift(name="drift3", ds=0.25, nslice=ns), monitor, ] # assign a fodo segment diff --git a/examples/fodo_programmable/run_fodo_programmable.py b/examples/fodo_programmable/run_fodo_programmable.py index dd9310608..b74e697f3 100755 --- a/examples/fodo_programmable/run_fodo_programmable.py +++ b/examples/fodo_programmable/run_fodo_programmable.py @@ -112,7 +112,7 @@ def my_ref_drift(pge, refpart): refpart.s = s + slice_ds -pge1 = elements.Programmable() +pge1 = elements.Programmable(name="d1") pge1.nslice = ns pge1.beam_particles = lambda pti, refpart: my_drift(pge1, pti, refpart) pge1.ref_particle = lambda refpart: my_ref_drift(pge1, refpart) @@ -121,7 +121,7 @@ def my_ref_drift(pge, refpart): # attention: assignment is a reference for pge2 = pge1 -pge2 = elements.Programmable() +pge2 = elements.Programmable(name="d2") pge2.nslice = ns pge2.beam_particles = lambda pti, refpart: my_drift(pge2, pti, refpart) pge2.ref_particle = lambda refpart: my_ref_drift(pge2, refpart) @@ -134,15 +134,15 @@ def my_ref_drift(pge, refpart): # design the accelerator lattice fodo = [ monitor, - pge1, # equivalent to elements.Drift(ds=0.25, nslice=ns) + pge1, # equivalent to elements.Drift("d1", ds=0.25, nslice=ns) monitor, - elements.Quad(ds=1.0, k=1.0, nslice=ns), + elements.Quad(name="q1", ds=1.0, k=1.0, nslice=ns), monitor, - pge2, # equivalent to elements.Drift(ds=0.5, nslice=ns) + pge2, # equivalent to elements.Drift("d2", ds=0.5, nslice=ns) monitor, - elements.Quad(ds=1.0, k=-1.0, nslice=ns), + elements.Quad(name="q2", ds=1.0, k=-1.0, nslice=ns), monitor, - pge1, # equivalent to elements.Drift(ds=0.25, nslice=ns) + pge1, # equivalent to elements.Drift("d1", ds=0.25, nslice=ns) monitor, ] # assign a fodo segment diff --git a/examples/fodo_rf/run_fodo_rf.py b/examples/fodo_rf/run_fodo_rf.py index 1eed9e10b..397e46212 100644 --- a/examples/fodo_rf/run_fodo_rf.py +++ b/examples/fodo_rf/run_fodo_rf.py @@ -47,12 +47,12 @@ # design the accelerator lattice sim.lattice.append(monitor) # Quad elements -quad1 = elements.Quad(ds=0.15, k=2.5) -quad2 = elements.Quad(ds=0.3, k=-2.5) +quad1 = elements.Quad(name="quad1", ds=0.15, k=2.5) +quad2 = elements.Quad(name="quad2", ds=0.3, k=-2.5) # Drift element -drift1 = elements.Drift(ds=1.0) +drift1 = elements.Drift(name="drift1", ds=1.0) # Short RF cavity element -shortrf1 = elements.Buncher(V=0.01, k=15.0) +shortrf1 = elements.Buncher(name="shortrf1", V=0.01, k=15.0) lattice_no_drifts = [quad1, shortrf1, quad2, shortrf1, quad1] # set first lattice element diff --git a/examples/fodo_tune/run_fodo_tune.py b/examples/fodo_tune/run_fodo_tune.py index 2315be15b..864a799d7 100755 --- a/examples/fodo_tune/run_fodo_tune.py +++ b/examples/fodo_tune/run_fodo_tune.py @@ -51,11 +51,11 @@ ns = 1 # number of slices per ds in the element fodo = [ monitor, - elements.Drift(ds=0.25, nslice=ns), - elements.Quad(ds=1.0, k=1.0, nslice=ns), - elements.Drift(ds=0.5, nslice=ns), - elements.Quad(ds=1.0, k=-1.0, nslice=ns), - elements.Drift(ds=0.25, nslice=ns), + elements.Drift(name="drift1", ds=0.25, nslice=ns), + elements.Quad(name="quad1", ds=1.0, k=1.0, nslice=ns), + elements.Drift(name="drift2", ds=0.5, nslice=ns), + elements.Quad(name="quad2", ds=1.0, k=-1.0, nslice=ns), + elements.Drift(name="drift3", ds=0.25, nslice=ns), ] # assign a fodo segment sim.lattice.extend(fodo) diff --git a/examples/initialize_from_array/run_from_array.py b/examples/initialize_from_array/run_from_array.py index c53eb3588..351ff95ab 100644 --- a/examples/initialize_from_array/run_from_array.py +++ b/examples/initialize_from_array/run_from_array.py @@ -98,7 +98,7 @@ sim.lattice.extend( [ monitor, - elements.Drift(ds=0.01), + elements.Drift(name="drift", ds=0.01), monitor, ] ) diff --git a/examples/iota_lattice/run_iotalattice.py b/examples/iota_lattice/run_iotalattice.py index 6cc6af2f0..5488cf1c7 100644 --- a/examples/iota_lattice/run_iotalattice.py +++ b/examples/iota_lattice/run_iotalattice.py @@ -46,57 +46,57 @@ ns = 10 # number of slices per ds in the element # Drift elements -dra1 = elements.Drift(ds=0.9125, nslice=ns) -dra2 = elements.Drift(ds=0.135, nslice=ns) -dra3 = elements.Drift(ds=0.725, nslice=ns) -dra4 = elements.Drift(ds=0.145, nslice=ns) -dra5 = elements.Drift(ds=0.3405, nslice=ns) -drb1 = elements.Drift(ds=0.3205, nslice=ns) -drb2 = elements.Drift(ds=0.14, nslice=ns) -drb3 = elements.Drift(ds=0.1525, nslice=ns) -drb4 = elements.Drift(ds=0.31437095, nslice=ns) -drc1 = elements.Drift(ds=0.42437095, nslice=ns) -drc2 = elements.Drift(ds=0.355, nslice=ns) -dnll = elements.Drift(ds=1.8, nslice=ns) -drd1 = elements.Drift(ds=0.62437095, nslice=ns) -drd2 = elements.Drift(ds=0.42, nslice=ns) -drd3 = elements.Drift(ds=1.625, nslice=ns) -drd4 = elements.Drift(ds=0.6305, nslice=ns) -dre1 = elements.Drift(ds=0.5305, nslice=ns) -dre2 = elements.Drift(ds=1.235, nslice=ns) -dre3 = elements.Drift(ds=0.8075, nslice=ns) +dra1 = elements.Drift(name="dra1", ds=0.9125, nslice=ns) +dra2 = elements.Drift(name="dra2", ds=0.135, nslice=ns) +dra3 = elements.Drift(name="dra3", ds=0.725, nslice=ns) +dra4 = elements.Drift(name="dra4", ds=0.145, nslice=ns) +dra5 = elements.Drift(name="dra5", ds=0.3405, nslice=ns) +drb1 = elements.Drift(name="drb1", ds=0.3205, nslice=ns) +drb2 = elements.Drift(name="drb2", ds=0.14, nslice=ns) +drb3 = elements.Drift(name="drb3", ds=0.1525, nslice=ns) +drb4 = elements.Drift(name="drb4", ds=0.31437095, nslice=ns) +drc1 = elements.Drift(name="drc1", ds=0.42437095, nslice=ns) +drc2 = elements.Drift(name="drc2", ds=0.355, nslice=ns) +dnll = elements.Drift(name="dnll", ds=1.8, nslice=ns) +drd1 = elements.Drift(name="drd1", ds=0.62437095, nslice=ns) +drd2 = elements.Drift(name="drd2", ds=0.42, nslice=ns) +drd3 = elements.Drift(name="drd3", ds=1.625, nslice=ns) +drd4 = elements.Drift(name="drd4", ds=0.6305, nslice=ns) +dre1 = elements.Drift(name="dre1", ds=0.5305, nslice=ns) +dre2 = elements.Drift(name="dre2", ds=1.235, nslice=ns) +dre3 = elements.Drift(name="dre3", ds=0.8075, nslice=ns) # Bend elements rc30 = 0.822230996255981 -sbend30 = elements.Sbend(ds=0.4305191429, rc=rc30) -edge30 = elements.DipEdge(psi=0.0, rc=rc30, g=0.058, K2=0.5) +sbend30 = elements.Sbend(name="sbend30", ds=0.4305191429, rc=rc30) +edge30 = elements.DipEdge(name="edge30", psi=0.0, rc=rc30, g=0.058, K2=0.5) rc60 = 0.772821121503940 -sbend60 = elements.Sbend(ds=0.8092963858, rc=rc60) -edge60 = elements.DipEdge(psi=0.0, rc=rc60, g=0.058, K2=0.5) +sbend60 = elements.Sbend(name="sbend60", ds=0.8092963858, rc=rc60) +edge60 = elements.DipEdge(name="edge60", psi=0.0, rc=rc60, g=0.058, K2=0.5) # Quad elements ds_quad = 0.21 -qa1 = elements.Quad(ds=ds_quad, k=-8.78017699, nslice=ns) -qa2 = elements.Quad(ds=ds_quad, k=13.24451745, nslice=ns) -qa3 = elements.Quad(ds=ds_quad, k=-13.65151327, nslice=ns) -qa4 = elements.Quad(ds=ds_quad, k=19.75138652, nslice=ns) -qb1 = elements.Quad(ds=ds_quad, k=-10.84199727, nslice=ns) -qb2 = elements.Quad(ds=ds_quad, k=16.24844348, nslice=ns) -qb3 = elements.Quad(ds=ds_quad, k=-8.27411104, nslice=ns) -qb4 = elements.Quad(ds=ds_quad, k=-7.45719247, nslice=ns) -qb5 = elements.Quad(ds=ds_quad, k=14.03362243, nslice=ns) -qb6 = elements.Quad(ds=ds_quad, k=-12.23595641, nslice=ns) -qc1 = elements.Quad(ds=ds_quad, k=-13.18863768, nslice=ns) -qc2 = elements.Quad(ds=ds_quad, k=11.50601829, nslice=ns) -qc3 = elements.Quad(ds=ds_quad, k=-11.10445869, nslice=ns) -qd1 = elements.Quad(ds=ds_quad, k=-6.78179218, nslice=ns) -qd2 = elements.Quad(ds=ds_quad, k=5.19026998, nslice=ns) -qd3 = elements.Quad(ds=ds_quad, k=-5.8586173, nslice=ns) -qd4 = elements.Quad(ds=ds_quad, k=4.62460039, nslice=ns) -qe1 = elements.Quad(ds=ds_quad, k=-4.49607687, nslice=ns) -qe2 = elements.Quad(ds=ds_quad, k=6.66737146, nslice=ns) -qe3 = elements.Quad(ds=ds_quad, k=-6.69148177, nslice=ns) +qa1 = elements.Quad(name="qa1", ds=ds_quad, k=-8.78017699, nslice=ns) +qa2 = elements.Quad(name="qa2", ds=ds_quad, k=13.24451745, nslice=ns) +qa3 = elements.Quad(name="qa3", ds=ds_quad, k=-13.65151327, nslice=ns) +qa4 = elements.Quad(name="qa4", ds=ds_quad, k=19.75138652, nslice=ns) +qb1 = elements.Quad(name="qb1", ds=ds_quad, k=-10.84199727, nslice=ns) +qb2 = elements.Quad(name="qb2", ds=ds_quad, k=16.24844348, nslice=ns) +qb3 = elements.Quad(name="qb3", ds=ds_quad, k=-8.27411104, nslice=ns) +qb4 = elements.Quad(name="qb4", ds=ds_quad, k=-7.45719247, nslice=ns) +qb5 = elements.Quad(name="qb5", ds=ds_quad, k=14.03362243, nslice=ns) +qb6 = elements.Quad(name="qb6", ds=ds_quad, k=-12.23595641, nslice=ns) +qc1 = elements.Quad(name="qc1", ds=ds_quad, k=-13.18863768, nslice=ns) +qc2 = elements.Quad(name="qc2", ds=ds_quad, k=11.50601829, nslice=ns) +qc3 = elements.Quad(name="qc3", ds=ds_quad, k=-11.10445869, nslice=ns) +qd1 = elements.Quad(name="qd1", ds=ds_quad, k=-6.78179218, nslice=ns) +qd2 = elements.Quad(name="qd2", ds=ds_quad, k=5.19026998, nslice=ns) +qd3 = elements.Quad(name="qd3", ds=ds_quad, k=-5.8586173, nslice=ns) +qd4 = elements.Quad(name="qd4", ds=ds_quad, k=4.62460039, nslice=ns) +qe1 = elements.Quad(name="qe1", ds=ds_quad, k=-4.49607687, nslice=ns) +qe2 = elements.Quad(name="qe2", ds=ds_quad, k=6.66737146, nslice=ns) +qe3 = elements.Quad(name="qe3", ds=ds_quad, k=-6.69148177, nslice=ns) # build lattice: first half, qe3, then mirror # fmt: off diff --git a/examples/iota_lattice/run_iotalattice_sdep.py b/examples/iota_lattice/run_iotalattice_sdep.py index 49cd567fa..70f50d101 100644 --- a/examples/iota_lattice/run_iotalattice_sdep.py +++ b/examples/iota_lattice/run_iotalattice_sdep.py @@ -56,57 +56,57 @@ ns = 10 # number of slices per ds in the element # Drift elements -dra1 = elements.Drift(ds=0.9125, nslice=ns) -dra2 = elements.Drift(ds=0.135, nslice=ns) -dra3 = elements.Drift(ds=0.725, nslice=ns) -dra4 = elements.Drift(ds=0.145, nslice=ns) -dra5 = elements.Drift(ds=0.3405, nslice=ns) -drb1 = elements.Drift(ds=0.3205, nslice=ns) -drb2 = elements.Drift(ds=0.14, nslice=ns) -drb3 = elements.Drift(ds=0.1525, nslice=ns) -drb4 = elements.Drift(ds=0.31437095, nslice=ns) -drc1 = elements.Drift(ds=0.42437095, nslice=ns) -drc2 = elements.Drift(ds=0.355, nslice=ns) -dnll = elements.Drift(ds=1.8, nslice=ns) -drd1 = elements.Drift(ds=0.62437095, nslice=ns) -drd2 = elements.Drift(ds=0.42, nslice=ns) -drd3 = elements.Drift(ds=1.625, nslice=ns) -drd4 = elements.Drift(ds=0.6305, nslice=ns) -dre1 = elements.Drift(ds=0.5305, nslice=ns) -dre2 = elements.Drift(ds=1.235, nslice=ns) -dre3 = elements.Drift(ds=0.8075, nslice=ns) +dra1 = elements.Drift(name="dra1", ds=0.9125, nslice=ns) +dra2 = elements.Drift(name="dra2", ds=0.135, nslice=ns) +dra3 = elements.Drift(name="dra3", ds=0.725, nslice=ns) +dra4 = elements.Drift(name="dra4", ds=0.145, nslice=ns) +dra5 = elements.Drift(name="dra5", ds=0.3405, nslice=ns) +drb1 = elements.Drift(name="drb1", ds=0.3205, nslice=ns) +drb2 = elements.Drift(name="drb2", ds=0.14, nslice=ns) +drb3 = elements.Drift(name="drb3", ds=0.1525, nslice=ns) +drb4 = elements.Drift(name="drb4", ds=0.31437095, nslice=ns) +drc1 = elements.Drift(name="drc1", ds=0.42437095, nslice=ns) +drc2 = elements.Drift(name="drc2", ds=0.355, nslice=ns) +dnll = elements.Drift(name="dnll", ds=1.8, nslice=ns) +drd1 = elements.Drift(name="drd1", ds=0.62437095, nslice=ns) +drd2 = elements.Drift(name="drd2", ds=0.42, nslice=ns) +drd3 = elements.Drift(name="drd3", ds=1.625, nslice=ns) +drd4 = elements.Drift(name="drd4", ds=0.6305, nslice=ns) +dre1 = elements.Drift(name="dre1", ds=0.5305, nslice=ns) +dre2 = elements.Drift(name="dre2", ds=1.235, nslice=ns) +dre3 = elements.Drift(name="dre3", ds=0.8075, nslice=ns) # Bend elements rc30 = 0.822230996255981 -sbend30 = elements.Sbend(ds=0.4305191429, rc=rc30) -edge30 = elements.DipEdge(psi=0.0, rc=rc30, g=0.058, K2=0.5) +sbend30 = elements.Sbend(name="sbend30", ds=0.4305191429, rc=rc30) +edge30 = elements.DipEdge(name="edge30", psi=0.0, rc=rc30, g=0.058, K2=0.5) rc60 = 0.772821121503940 -sbend60 = elements.Sbend(ds=0.8092963858, rc=rc60) -edge60 = elements.DipEdge(psi=0.0, rc=rc60, g=0.058, K2=0.5) +sbend60 = elements.Sbend(name="sbend60", ds=0.8092963858, rc=rc60) +edge60 = elements.DipEdge(name="edge60", psi=0.0, rc=rc60, g=0.058, K2=0.5) # Quad elements ds_quad = 0.21 -qa1 = elements.Quad(ds=ds_quad, k=-8.78017699, nslice=ns) -qa2 = elements.Quad(ds=ds_quad, k=13.24451745, nslice=ns) -qa3 = elements.Quad(ds=ds_quad, k=-13.65151327, nslice=ns) -qa4 = elements.Quad(ds=ds_quad, k=19.75138652, nslice=ns) -qb1 = elements.Quad(ds=ds_quad, k=-10.84199727, nslice=ns) -qb2 = elements.Quad(ds=ds_quad, k=16.24844348, nslice=ns) -qb3 = elements.Quad(ds=ds_quad, k=-8.27411104, nslice=ns) -qb4 = elements.Quad(ds=ds_quad, k=-7.45719247, nslice=ns) -qb5 = elements.Quad(ds=ds_quad, k=14.03362243, nslice=ns) -qb6 = elements.Quad(ds=ds_quad, k=-12.23595641, nslice=ns) -qc1 = elements.Quad(ds=ds_quad, k=-13.18863768, nslice=ns) -qc2 = elements.Quad(ds=ds_quad, k=11.50601829, nslice=ns) -qc3 = elements.Quad(ds=ds_quad, k=-11.10445869, nslice=ns) -qd1 = elements.Quad(ds=ds_quad, k=-6.78179218, nslice=ns) -qd2 = elements.Quad(ds=ds_quad, k=5.19026998, nslice=ns) -qd3 = elements.Quad(ds=ds_quad, k=-5.8586173, nslice=ns) -qd4 = elements.Quad(ds=ds_quad, k=4.62460039, nslice=ns) -qe1 = elements.Quad(ds=ds_quad, k=-4.49607687, nslice=ns) -qe2 = elements.Quad(ds=ds_quad, k=6.66737146, nslice=ns) -qe3 = elements.Quad(ds=ds_quad, k=-6.69148177, nslice=ns) +qa1 = elements.Quad(name="qa1", ds=ds_quad, k=-8.78017699, nslice=ns) +qa2 = elements.Quad(name="qa2", ds=ds_quad, k=13.24451745, nslice=ns) +qa3 = elements.Quad(name="qa3", ds=ds_quad, k=-13.65151327, nslice=ns) +qa4 = elements.Quad(name="qa4", ds=ds_quad, k=19.75138652, nslice=ns) +qb1 = elements.Quad(name="qb1", ds=ds_quad, k=-10.84199727, nslice=ns) +qb2 = elements.Quad(name="qb2", ds=ds_quad, k=16.24844348, nslice=ns) +qb3 = elements.Quad(name="qb3", ds=ds_quad, k=-8.27411104, nslice=ns) +qb4 = elements.Quad(name="qb4", ds=ds_quad, k=-7.45719247, nslice=ns) +qb5 = elements.Quad(name="qb5", ds=ds_quad, k=14.03362243, nslice=ns) +qb6 = elements.Quad(name="qb6", ds=ds_quad, k=-12.23595641, nslice=ns) +qc1 = elements.Quad(name="qc1", ds=ds_quad, k=-13.18863768, nslice=ns) +qc2 = elements.Quad(name="qc2", ds=ds_quad, k=11.50601829, nslice=ns) +qc3 = elements.Quad(name="qc3", ds=ds_quad, k=-11.10445869, nslice=ns) +qd1 = elements.Quad(name="qd1", ds=ds_quad, k=-6.78179218, nslice=ns) +qd2 = elements.Quad(name="qd2", ds=ds_quad, k=5.19026998, nslice=ns) +qd3 = elements.Quad(name="qd3", ds=ds_quad, k=-5.8586173, nslice=ns) +qd4 = elements.Quad(name="qd4", ds=ds_quad, k=4.62460039, nslice=ns) +qe1 = elements.Quad(name="qe1", ds=ds_quad, k=-4.49607687, nslice=ns) +qe2 = elements.Quad(name="qe2", ds=ds_quad, k=6.66737146, nslice=ns) +qe3 = elements.Quad(name="qe3", ds=ds_quad, k=-6.69148177, nslice=ns) # Special (elliptic) nonlinear element: @@ -120,7 +120,7 @@ # build up the nonlinear lens in segments ds_half = ds / 2.0 -dr = elements.Drift(ds=ds_half, nslice=1) +dr = elements.Drift(name="dr", ds=ds_half, nslice=1) nll = [] for j in range(0, num_lenses): s = -lens_length / 2.0 + ds_half + j * ds @@ -130,7 +130,7 @@ ) knll_s = t_strength * c_parameter**2 * ds / beta cnll_s = c_parameter * math.sqrt(beta) - nllens = elements.NonlinearLens(knll=knll_s, cnll=cnll_s) + nllens = elements.NonlinearLens(name="nllens" + str(j), knll=knll_s, cnll=cnll_s) segment = [dr, nllens, dr] nll.extend(segment) diff --git a/examples/iota_lens/run_iotalens.py b/examples/iota_lens/run_iotalens.py index 96a02118f..3efcd826f 100644 --- a/examples/iota_lens/run_iotalens.py +++ b/examples/iota_lens/run_iotalens.py @@ -48,9 +48,9 @@ monitor.cn = 0.01 # design the accelerator lattice -constEnd = elements.ConstF(ds=0.05, kx=1.0, ky=1.0, kt=1.0e-12) -nllens = elements.NonlinearLens(knll=4.0e-6, cnll=0.01) -const = elements.ConstF(ds=0.1, kx=1.0, ky=1.0, kt=1.0e-12) +constEnd = elements.ConstF(name="constEnd", ds=0.05, kx=1.0, ky=1.0, kt=1.0e-12) +nllens = elements.NonlinearLens(name="nllens", knll=4.0e-6, cnll=0.01) +const = elements.ConstF(name="const", ds=0.1, kx=1.0, ky=1.0, kt=1.0e-12) num_lenses = 18 nllens_lattice = ( diff --git a/examples/iota_lens/run_iotalens_sdep.py b/examples/iota_lens/run_iotalens_sdep.py index a078a27b0..40f147062 100644 --- a/examples/iota_lens/run_iotalens_sdep.py +++ b/examples/iota_lens/run_iotalens_sdep.py @@ -65,7 +65,7 @@ # drift elements ds_half = ds / 2.0 -dr = elements.Drift(ds=ds_half) +dr = elements.Drift(name="dr", ds=ds_half) # define the nonlinear lens segments for j in range(0, num_lenses): @@ -76,13 +76,18 @@ ) knll_s = t_strength * c_parameter**2 * ds / beta cnll_s = c_parameter * math.sqrt(beta) - nllens = elements.NonlinearLens(knll=knll_s, cnll=cnll_s) + nllens = elements.NonlinearLens(name="nllens" + str(j), knll=knll_s, cnll=cnll_s) segments = [dr, nllens, dr] sim.lattice.extend(segments) # focusing lens const = elements.ConstF( - ds=1.0e-8, kx=12060.113295833, ky=12060.113295833, kt=1.0e-12, nslice=1 + name="const", + ds=1.0e-8, + kx=12060.113295833, + ky=12060.113295833, + kt=1.0e-12, + nslice=1, ) sim.lattice.append(const) sim.lattice.append(monitor) diff --git a/examples/kicker/run_kicker.py b/examples/kicker/run_kicker.py index 77cc624ea..c21a00f6c 100644 --- a/examples/kicker/run_kicker.py +++ b/examples/kicker/run_kicker.py @@ -46,8 +46,8 @@ # design the accelerator lattice kicklattice = [ monitor, - elements.Kicker(xkick=2.0e-3, ykick=0.0, unit="dimensionless"), - elements.Kicker(xkick=0.0, ykick=3.0e-3, unit="dimensionless"), + elements.Kicker(name="kick1", xkick=2.0e-3, ykick=0.0, unit="dimensionless"), + elements.Kicker(name="kick2", xkick=0.0, ykick=3.0e-3, unit="dimensionless"), monitor, ] # assign a lattice diff --git a/examples/kurth/run_kurth_10nC_periodic.py b/examples/kurth/run_kurth_10nC_periodic.py index b819a8ecb..62b3be319 100755 --- a/examples/kurth/run_kurth_10nC_periodic.py +++ b/examples/kurth/run_kurth_10nC_periodic.py @@ -47,8 +47,8 @@ # design the accelerator lattice nslice = 20 # use 30 for increased precision -constf1 = elements.ConstF(ds=2.0, kx=0.7, ky=0.7, kt=0.7, nslice=nslice) -drift1 = elements.Drift(ds=1.0, nslice=nslice) +constf1 = elements.ConstF(name="constf1", ds=2.0, kx=0.7, ky=0.7, kt=0.7, nslice=nslice) +drift1 = elements.Drift(name="drift1", ds=1.0, nslice=nslice) sim.lattice.extend([monitor, drift1, constf1, drift1, monitor]) # run simulation diff --git a/examples/kurth/run_kurth_periodic.py b/examples/kurth/run_kurth_periodic.py index 1d4d45aa1..6b6f60f0d 100755 --- a/examples/kurth/run_kurth_periodic.py +++ b/examples/kurth/run_kurth_periodic.py @@ -45,8 +45,8 @@ monitor = elements.BeamMonitor("monitor", backend="h5") # design the accelerator lattice -constf1 = elements.ConstF(ds=2.0, kx=0.7, ky=0.7, kt=0.7) -drift1 = elements.Drift(ds=1.0) +constf1 = elements.ConstF(name="constf1", ds=2.0, kx=0.7, ky=0.7, kt=0.7) +drift1 = elements.Drift(name="drift1", ds=1.0) sim.lattice.extend([monitor, drift1, constf1, drift1, monitor]) # run simulation diff --git a/examples/multipole/run_multipole.py b/examples/multipole/run_multipole.py index 656f751cb..a8a065351 100644 --- a/examples/multipole/run_multipole.py +++ b/examples/multipole/run_multipole.py @@ -46,9 +46,11 @@ # design the accelerator lattice multipole = [ monitor, - elements.Multipole(multipole=2, K_normal=3.0, K_skew=0.0), - elements.Multipole(multipole=3, K_normal=100.0, K_skew=-50.0), - elements.Multipole(multipole=4, K_normal=65.0, K_skew=6.0), + elements.Multipole(name="thin_quadrupole", multipole=2, K_normal=3.0, K_skew=0.0), + elements.Multipole( + name="thin_sextupole", multipole=3, K_normal=100.0, K_skew=-50.0 + ), + elements.Multipole(name="thin_octupole", multipole=4, K_normal=65.0, K_skew=6.0), monitor, ] # assign a fodo segment diff --git a/examples/optimize_triplet/run_triplet.py b/examples/optimize_triplet/run_triplet.py index 0766e315a..dc7466d15 100755 --- a/examples/optimize_triplet/run_triplet.py +++ b/examples/optimize_triplet/run_triplet.py @@ -43,13 +43,13 @@ def build_lattice(parameters: tuple, write_particles: bool) -> list: # enforce a mirror symmetry of the triplet line = [ - elements.Drift(ds=2.7, nslice=ns), - elements.Quad(ds=0.1, k=q1_k, nslice=ns), - elements.Drift(ds=1.4, nslice=ns), - elements.Quad(ds=0.2, k=q2_k, nslice=ns), - elements.Drift(ds=1.4, nslice=ns), - elements.Quad(ds=0.1, k=q1_k, nslice=ns), - elements.Drift(ds=2.7, nslice=ns), + elements.Drift(name="drift1", ds=2.7, nslice=ns), + elements.Quad(name="quad1", ds=0.1, k=q1_k, nslice=ns), + elements.Drift(name="drift2", ds=1.4, nslice=ns), + elements.Quad(name="quad2", ds=0.2, k=q2_k, nslice=ns), + elements.Drift(name="drift3", ds=1.4, nslice=ns), + elements.Quad(name="quad1", ds=0.1, k=q1_k, nslice=ns), + elements.Drift(name="drift4", ds=2.7, nslice=ns), ] if write_particles: diff --git a/examples/positron_channel/run_positron.py b/examples/positron_channel/run_positron.py index 069edcc0f..0f98ae161 100755 --- a/examples/positron_channel/run_positron.py +++ b/examples/positron_channel/run_positron.py @@ -50,14 +50,16 @@ ns = 1 # number of slices per ds in the element period = [ monitor, - elements.ChrQuad(ds=0.1, k=-6.674941, unit=1, nslice=ns), - elements.ChrDrift(ds=0.3, nslice=ns), - elements.ChrQuad(ds=0.2, k=6.674941, unit=1, nslice=ns), - elements.ChrDrift(ds=0.3, nslice=ns), - elements.ChrQuad(ds=0.1, k=-6.674941, unit=1, nslice=ns), - elements.ChrDrift(ds=0.1, nslice=ns), - elements.ChrAcc(ds=1.8, ez=10871.950994502130424, bz=1.0e-12, nslice=ns), - elements.ChrDrift(ds=0.1, nslice=ns), + elements.ChrQuad(name="quad1", ds=0.1, k=-6.674941, unit=1, nslice=ns), + elements.ChrDrift(name="drift1", ds=0.3, nslice=ns), + elements.ChrQuad(name="quad2", ds=0.2, k=6.674941, unit=1, nslice=ns), + elements.ChrDrift(name="drift2", ds=0.3, nslice=ns), + elements.ChrQuad(name="quad3", ds=0.1, k=-6.674941, unit=1, nslice=ns), + elements.ChrDrift(name="drift3", ds=0.1, nslice=ns), + elements.ChrAcc( + name="acc", ds=1.8, ez=10871.950994502130424, bz=1.0e-12, nslice=ns + ), + elements.ChrDrift(name="drift4", ds=0.1, nslice=ns), monitor, ] diff --git a/examples/pytorch_surrogate_model/run_ml_surrogate_15_stage.py b/examples/pytorch_surrogate_model/run_ml_surrogate_15_stage.py index 94c205c80..004f79bdb 100644 --- a/examples/pytorch_surrogate_model/run_ml_surrogate_15_stage.py +++ b/examples/pytorch_surrogate_model/run_ml_surrogate_15_stage.py @@ -169,7 +169,7 @@ def __init__(self, stage_i, surrogate_model, surrogate_length, stage_start): self.push = self.surrogate_push self.ds = surrogate_length - def surrogate_push(self, pc, step): + def surrogate_push(self, pc, step, period): ref_part = pc.ref_particle() ref_z_i = ref_part.z ref_z_i_LPA = ref_z_i - self.stage_start @@ -304,7 +304,7 @@ def __init__(self, sim, stage_i, lattice_index, x_or_y): self.x_or_y = x_or_y self.push = self.set_lens - def set_lens(self, pc, step): + def set_lens(self, pc, step, period): # get envelope parameters rbc = pc.reduced_beam_characteristics() alpha = rbc[f"alpha_{self.x_or_y}"] diff --git a/examples/quadrupole_softedge/run_quadrupole_softedge.py b/examples/quadrupole_softedge/run_quadrupole_softedge.py index 055d19080..8f74a3241 100755 --- a/examples/quadrupole_softedge/run_quadrupole_softedge.py +++ b/examples/quadrupole_softedge/run_quadrupole_softedge.py @@ -50,6 +50,7 @@ ns = 1 # number of slices per ds in the element quad1 = elements.SoftQuadrupole( + name="quad1", ds=1.0, gscale=1.0, cos_coefficients=[2], @@ -59,6 +60,7 @@ ) quad2 = elements.SoftQuadrupole( + name="quad2", ds=1.0, gscale=-1.0, cos_coefficients=[2], @@ -67,8 +69,8 @@ nslice=ns, ) -drift1 = elements.Drift(ds=0.25, nslice=ns) -drift2 = elements.Drift(ds=0.5, nslice=ns) +drift1 = elements.Drift(name="drift1", ds=0.25, nslice=ns) +drift2 = elements.Drift(name="drift2", ds=0.5, nslice=ns) # assign a fodo segment sim.lattice.extend([monitor, drift1, quad1, drift2, quad2, drift1, monitor]) diff --git a/examples/rfcavity/run_rfcavity.py b/examples/rfcavity/run_rfcavity.py index acdba68b4..af73ed12c 100755 --- a/examples/rfcavity/run_rfcavity.py +++ b/examples/rfcavity/run_rfcavity.py @@ -47,10 +47,11 @@ # design the accelerator lattice # Drift elements -dr1 = elements.Drift(ds=0.4, nslice=1) -dr2 = elements.Drift(ds=0.032997, nslice=1) +dr1 = elements.Drift(name="dr1", ds=0.4, nslice=1) +dr2 = elements.Drift(name="dr2", ds=0.032997, nslice=1) # RF cavity element rf = elements.RFCavity( + name="rf", ds=1.31879807, escale=62.0, freq=1.3e9, diff --git a/examples/rotation/run_rotation.py b/examples/rotation/run_rotation.py index a1257b302..0f1f187d1 100644 --- a/examples/rotation/run_rotation.py +++ b/examples/rotation/run_rotation.py @@ -46,9 +46,9 @@ # design the accelerator lattice rotated_drift = [ monitor, - elements.PRot(phi_in=0.0, phi_out=-5.0), - elements.Drift(ds=2.0, nslice=1), - elements.PRot(phi_in=-5.0, phi_out=0.0), + elements.PRot(name="rotation1", phi_in=0.0, phi_out=-5.0), + elements.Drift(name="drift1", ds=2.0, nslice=1), + elements.PRot(name="rotation2", phi_in=-5.0, phi_out=0.0), monitor, ] # assign a lattice segment diff --git a/examples/solenoid/run_solenoid.py b/examples/solenoid/run_solenoid.py index 78f02100a..fc0c517b4 100755 --- a/examples/solenoid/run_solenoid.py +++ b/examples/solenoid/run_solenoid.py @@ -48,7 +48,7 @@ sim.lattice.extend( [ monitor, - elements.Sol(ds=3.820395, ks=0.8223219329893234), + elements.Sol(name="sol1", ds=3.820395, ks=0.8223219329893234), monitor, ] ) diff --git a/examples/solenoid_softedge/run_solenoid_softedge.py b/examples/solenoid_softedge/run_solenoid_softedge.py index 0d93a7f31..c56aa236c 100755 --- a/examples/solenoid_softedge/run_solenoid_softedge.py +++ b/examples/solenoid_softedge/run_solenoid_softedge.py @@ -43,6 +43,7 @@ # design the accelerator lattice sol = elements.SoftSolenoid( + name="sol1", ds=6.0, bscale=1.233482899483985, cos_coefficients=[ diff --git a/examples/thin_dipole/run_thin_dipole.py b/examples/thin_dipole/run_thin_dipole.py index e29b320e7..4df695f9a 100755 --- a/examples/thin_dipole/run_thin_dipole.py +++ b/examples/thin_dipole/run_thin_dipole.py @@ -48,13 +48,15 @@ # design the accelerator lattice) ns = 1 # number of slices per ds in the element segment = [ - elements.Drift(ds=0.003926990816987, nslice=ns), - elements.ThinDipole(theta=0.45, rc=1.0), - elements.Drift(ds=0.003926990816987, nslice=ns), + elements.Drift(name="drift1", ds=0.003926990816987, nslice=ns), + elements.ThinDipole(name="kick", theta=0.45, rc=1.0), + elements.Drift(name="drift2", ds=0.003926990816987, nslice=ns), ] bend = 200 * segment -inverse_bend = elements.ExactSbend(ds=-1.570796326794897, phi=-90.0) +inverse_bend = elements.ExactSbend( + name="inverse_bend", ds=-1.570796326794897, phi=-90.0 +) sim.lattice.append(monitor) sim.lattice.extend(bend) diff --git a/setup.py b/setup.py index 308a4a0c6..5a398953f 100644 --- a/setup.py +++ b/setup.py @@ -223,7 +223,7 @@ def build_extension(self, ext): setup( name="impactx", # note PEP-440 syntax: x.y.zaN but x.y.z.devN - version="24.08", + version="24.09", packages=["impactx"], # Python sources: package_dir={"": "src/python"}, diff --git a/src/ImpactX.cpp b/src/ImpactX.cpp index 7466356af..ed08201f3 100644 --- a/src/ImpactX.cpp +++ b/src/ImpactX.cpp @@ -137,7 +137,7 @@ namespace impactx { // a global step for diagnostics including space charge slice steps in elements // before we start the evolve loop, we are in "step 0" (initial state) - int global_step = 0; + int step = 0; // check typos in inputs after step 1 bool early_params_checked = false; @@ -158,7 +158,7 @@ namespace impactx { diagnostics::DiagnosticOutput(*amr_data->m_particle_container, diagnostics::OutputType::PrintRefParticle, "diags/ref_particle", - global_step); + step); // print the initial values of reduced beam characteristics diagnostics::DiagnosticOutput(*amr_data->m_particle_container, @@ -181,10 +181,10 @@ namespace impactx { } // periods through the lattice - int periods = 1; - amrex::ParmParse("lattice").queryAdd("periods", periods); + int num_periods = 1; + amrex::ParmParse("lattice").queryAdd("periods", num_periods); - for (int cycle=0; cycle < periods; ++cycle) { + for (int period=0; period < num_periods; ++period) { // loop over all beamline elements for (auto &element_variant: m_lattice) { // update element edge of the reference particle @@ -201,9 +201,9 @@ namespace impactx { // sub-steps for space charge within the element for (int slice_step = 0; slice_step < nslice; ++slice_step) { BL_PROFILE("ImpactX::evolve::slice_step"); - global_step++; + step++; if (verbose > 0) { - amrex::Print() << " ++++ Starting global_step=" << global_step + amrex::Print() << " ++++ Starting step=" << step << " slice_step=" << slice_step << "\n"; } @@ -263,7 +263,7 @@ namespace impactx { // assuming that the distribution did not change // push all particles with external maps - Push(*amr_data->m_particle_container, element_variant, global_step); + Push(*amr_data->m_particle_container, element_variant, step, period); // move "lost" particles to another particle container collect_lost_particles(*amr_data->m_particle_container); @@ -282,14 +282,14 @@ namespace impactx { diagnostics::DiagnosticOutput(*amr_data->m_particle_container, diagnostics::OutputType::PrintRefParticle, "diags/ref_particle", - global_step, + step, true); // print slice step reduced beam characteristics to file diagnostics::DiagnosticOutput(*amr_data->m_particle_container, diagnostics::OutputType::PrintReducedBeamCharacteristics, "diags/reduced_beam_characteristics", - global_step, + step, true); } @@ -308,13 +308,13 @@ namespace impactx { diagnostics::DiagnosticOutput(*amr_data->m_particle_container, diagnostics::OutputType::PrintRefParticle, "diags/ref_particle_final", - global_step); + step); // print the final values of the reduced beam characteristics diagnostics::DiagnosticOutput(*amr_data->m_particle_container, diagnostics::OutputType::PrintReducedBeamCharacteristics, "diags/reduced_beam_characteristics_final", - global_step); + step); // output particles lost in apertures if (amr_data->m_particles_lost->TotalNumberOfParticles() > 0) @@ -323,7 +323,7 @@ namespace impactx { pp_diag.queryAdd("backend", openpmd_backend); diagnostics::BeamMonitor output_lost("particles_lost", openpmd_backend, "g"); - output_lost(*amr_data->m_particles_lost, 0); + output_lost(*amr_data->m_particles_lost, 0, 0); output_lost.finalize(); } } diff --git a/src/initialization/InitDistribution.cpp b/src/initialization/InitDistribution.cpp index 50f7b852e..61cdd77aa 100644 --- a/src/initialization/InitDistribution.cpp +++ b/src/initialization/InitDistribution.cpp @@ -266,19 +266,14 @@ namespace impactx pp_dist.get("distribution", distribution_type); std::string base_dist_type = distribution_type; - // Position of the underscore for splitting off the suffix in case the distribution name either ends in "_from_twiss" or "_from_cs" + // Position of the underscore for splitting off the suffix in case the distribution name either ends in "_from_twiss" std::size_t str_pos_from_twiss = distribution_type.rfind("_from_twiss"); - std::size_t str_pos_from_cs = distribution_type.rfind("_from_cs"); bool initialize_from_twiss = false; if (str_pos_from_twiss != std::string::npos) { // "_from_twiss" is found // Calculate suffix and base type, consider length of "_from_twiss" = 12 base_dist_type = distribution_type.substr(0, str_pos_from_twiss); initialize_from_twiss = true; - } else if (str_pos_from_cs != std::string::npos) { // "_from_cs" is found - // Calculate suffix and base type, consider length of "_from_cs" = 8 - base_dist_type = distribution_type.substr(0, str_pos_from_cs); - initialize_from_twiss = true; } /* After separating a potential suffix from its base type, check if the base distribution type is in the set of diff --git a/src/initialization/InitElement.cpp b/src/initialization/InitElement.cpp index 9c5afd6b6..71ec7e4aa 100644 --- a/src/initialization/InitElement.cpp +++ b/src/initialization/InitElement.cpp @@ -123,13 +123,13 @@ namespace detail amrex::ParticleReal k; pp_element.get("k", k); - m_lattice.emplace_back( Quad(ds, k, a["dx"], a["dy"], a["rotation_degree"], nslice) ); + m_lattice.emplace_back( Quad(ds, k, a["dx"], a["dy"], a["rotation_degree"], nslice, element_name) ); } else if (element_type == "drift") { auto const [ds, nslice] = detail::query_ds(pp_element, nslice_default); auto a = detail::query_alignment(pp_element); - m_lattice.emplace_back( Drift(ds, a["dx"], a["dy"], a["rotation_degree"], nslice) ); + m_lattice.emplace_back( Drift(ds, a["dx"], a["dy"], a["rotation_degree"], nslice, element_name) ); } else if (element_type == "sbend") { auto const [ds, nslice] = detail::query_ds(pp_element, nslice_default); @@ -138,7 +138,7 @@ namespace detail amrex::ParticleReal rc; pp_element.get("rc", rc); - m_lattice.emplace_back( Sbend(ds, rc, a["dx"], a["dy"], a["rotation_degree"], nslice) ); + m_lattice.emplace_back( Sbend(ds, rc, a["dx"], a["dy"], a["rotation_degree"], nslice, element_name) ); } else if (element_type == "cfbend") { auto const [ds, nslice] = detail::query_ds(pp_element, nslice_default); @@ -148,7 +148,7 @@ namespace detail pp_element.get("rc", rc); pp_element.get("k", k); - m_lattice.emplace_back( CFbend(ds, rc, k, a["dx"], a["dy"], a["rotation_degree"], nslice) ); + m_lattice.emplace_back( CFbend(ds, rc, k, a["dx"], a["dy"], a["rotation_degree"], nslice, element_name) ); } else if (element_type == "dipedge") { auto a = detail::query_alignment(pp_element); @@ -159,7 +159,7 @@ namespace detail pp_element.get("g", g); pp_element.get("K2", K2); - m_lattice.emplace_back( DipEdge(psi, rc, g, K2, a["dx"], a["dy"], a["rotation_degree"]) ); + m_lattice.emplace_back( DipEdge(psi, rc, g, K2, a["dx"], a["dy"], a["rotation_degree"], element_name) ); } else if (element_type == "constf") { auto const [ds, nslice] = detail::query_ds(pp_element, nslice_default); @@ -170,7 +170,7 @@ namespace detail pp_element.get("ky", ky); pp_element.get("kt", kt); - m_lattice.emplace_back( ConstF(ds, kx, ky, kt, a["dx"], a["dy"], a["rotation_degree"], nslice) ); + m_lattice.emplace_back( ConstF(ds, kx, ky, kt, a["dx"], a["dy"], a["rotation_degree"], nslice, element_name) ); } else if (element_type == "buncher") { auto a = detail::query_alignment(pp_element); @@ -179,7 +179,7 @@ namespace detail pp_element.get("V", V); pp_element.get("k", k); - m_lattice.emplace_back( Buncher(V, k, a["dx"], a["dy"], a["rotation_degree"]) ); + m_lattice.emplace_back( Buncher(V, k, a["dx"], a["dy"], a["rotation_degree"], element_name) ); } else if (element_type == "shortrf") { auto a = detail::query_alignment(pp_element); @@ -190,7 +190,7 @@ namespace detail pp_element.get("freq", freq); pp_element.queryAdd("phase", phase); - m_lattice.emplace_back( ShortRF(V, freq, phase, a["dx"], a["dy"], a["rotation_degree"]) ); + m_lattice.emplace_back( ShortRF(V, freq, phase, a["dx"], a["dy"], a["rotation_degree"], element_name) ); } else if (element_type == "multipole") { auto a = detail::query_alignment(pp_element); @@ -201,7 +201,7 @@ namespace detail pp_element.get("k_normal", k_normal); pp_element.get("k_skew", k_skew); - m_lattice.emplace_back( Multipole(m, k_normal, k_skew, a["dx"], a["dy"], a["rotation_degree"]) ); + m_lattice.emplace_back( Multipole(m, k_normal, k_skew, a["dx"], a["dy"], a["rotation_degree"], element_name) ); } else if (element_type == "nonlinear_lens") { auto a = detail::query_alignment(pp_element); @@ -210,7 +210,7 @@ namespace detail pp_element.get("knll", knll); pp_element.get("cnll", cnll); - m_lattice.emplace_back( NonlinearLens(knll, cnll, a["dx"], a["dy"], a["rotation_degree"]) ); + m_lattice.emplace_back( NonlinearLens(knll, cnll, a["dx"], a["dy"], a["rotation_degree"], element_name) ); } else if (element_type == "rfcavity") { auto const [ds, nslice] = detail::query_ds(pp_element, nslice_default); @@ -228,7 +228,7 @@ namespace detail detail::queryAddResize(pp_element, "cos_coefficients", cos_coef); detail::queryAddResize(pp_element, "sin_coefficients", sin_coef); - m_lattice.emplace_back( RFCavity(ds, escale, freq, phase, cos_coef, sin_coef, a["dx"], a["dy"], a["rotation_degree"], mapsteps, nslice) ); + m_lattice.emplace_back( RFCavity(ds, escale, freq, phase, cos_coef, sin_coef, a["dx"], a["dy"], a["rotation_degree"], mapsteps, nslice, element_name) ); } else if (element_type == "solenoid") { auto const [ds, nslice] = detail::query_ds(pp_element, nslice_default); @@ -237,14 +237,14 @@ namespace detail amrex::ParticleReal ks; pp_element.get("ks", ks); - m_lattice.emplace_back( Sol(ds, ks, a["dx"], a["dy"], a["rotation_degree"], nslice) ); + m_lattice.emplace_back( Sol(ds, ks, a["dx"], a["dy"], a["rotation_degree"], nslice, element_name) ); } else if (element_type == "prot") { amrex::ParticleReal phi_in, phi_out; pp_element.get("phi_in", phi_in); pp_element.get("phi_out", phi_out); - m_lattice.emplace_back( PRot(phi_in, phi_out) ); + m_lattice.emplace_back( PRot(phi_in, phi_out, element_name) ); } else if (element_type == "solenoid_softedge") { auto const [ds, nslice] = detail::query_ds(pp_element, nslice_default); @@ -262,7 +262,7 @@ namespace detail detail::queryAddResize(pp_element, "cos_coefficients", cos_coef); detail::queryAddResize(pp_element, "sin_coefficients", sin_coef); - m_lattice.emplace_back( SoftSolenoid(ds, bscale, cos_coef, sin_coef, units, a["dx"], a["dy"], a["rotation_degree"], mapsteps, nslice) ); + m_lattice.emplace_back( SoftSolenoid(ds, bscale, cos_coef, sin_coef, units, a["dx"], a["dy"], a["rotation_degree"], mapsteps, nslice, element_name) ); } else if (element_type == "quadrupole_softedge") { auto const [ds, nslice] = detail::query_ds(pp_element, nslice_default); @@ -278,13 +278,13 @@ namespace detail detail::queryAddResize(pp_element, "cos_coefficients", cos_coef); detail::queryAddResize(pp_element, "sin_coefficients", sin_coef); - m_lattice.emplace_back( SoftQuadrupole(ds, gscale, cos_coef, sin_coef, a["dx"], a["dy"], a["rotation_degree"], mapsteps, nslice) ); + m_lattice.emplace_back( SoftQuadrupole(ds, gscale, cos_coef, sin_coef, a["dx"], a["dy"], a["rotation_degree"], mapsteps, nslice, element_name) ); } else if (element_type == "drift_chromatic") { auto const [ds, nslice] = detail::query_ds(pp_element, nslice_default); auto a = detail::query_alignment(pp_element); - m_lattice.emplace_back( ChrDrift(ds, a["dx"], a["dy"], a["rotation_degree"], nslice) ); + m_lattice.emplace_back( ChrDrift(ds, a["dx"], a["dy"], a["rotation_degree"], nslice, element_name) ); } else if (element_type == "quad_chromatic") { auto a = detail::query_alignment(pp_element); @@ -295,7 +295,7 @@ namespace detail pp_element.get("k", k); pp_element.queryAdd("units", units); - m_lattice.emplace_back( ChrQuad(ds, k, units, a["dx"], a["dy"], a["rotation_degree"], nslice) ); + m_lattice.emplace_back( ChrQuad(ds, k, units, a["dx"], a["dy"], a["rotation_degree"], nslice, element_name) ); } else if (element_type == "plasma_lens_chromatic") { auto a = detail::query_alignment(pp_element); @@ -306,7 +306,7 @@ namespace detail pp_element.get("k", k); pp_element.queryAdd("units", units); - m_lattice.emplace_back( ChrPlasmaLens(ds, k, units, a["dx"], a["dy"], a["rotation_degree"], nslice) ); + m_lattice.emplace_back( ChrPlasmaLens(ds, k, units, a["dx"], a["dy"], a["rotation_degree"], nslice, element_name) ); } else if (element_type == "tapered_plasma_lens") { auto a = detail::query_alignment(pp_element); @@ -318,13 +318,13 @@ namespace detail pp_element.get("taper", taper); pp_element.queryAdd("units", units); - m_lattice.emplace_back( TaperedPL(k, taper, units, a["dx"], a["dy"], a["rotation_degree"]) ); + m_lattice.emplace_back( TaperedPL(k, taper, units, a["dx"], a["dy"], a["rotation_degree"], element_name) ); } else if (element_type == "drift_exact") { auto const [ds, nslice] = detail::query_ds(pp_element, nslice_default); auto a = detail::query_alignment(pp_element); - m_lattice.emplace_back( ExactDrift(ds, a["dx"], a["dy"], a["rotation_degree"], nslice) ); + m_lattice.emplace_back( ExactDrift(ds, a["dx"], a["dy"], a["rotation_degree"], nslice, element_name) ); } else if (element_type == "sbend_exact") { auto const [ds, nslice] = detail::query_ds(pp_element, nslice_default); @@ -335,7 +335,7 @@ namespace detail pp_element.get("phi", phi); pp_element.queryAdd("B", B); - m_lattice.emplace_back( ExactSbend(ds, phi, B, a["dx"], a["dy"], a["rotation_degree"], nslice) ); + m_lattice.emplace_back( ExactSbend(ds, phi, B, a["dx"], a["dy"], a["rotation_degree"], nslice, element_name) ); } else if (element_type == "uniform_acc_chromatic") { auto const [ds, nslice] = detail::query_ds(pp_element, nslice_default); @@ -345,7 +345,7 @@ namespace detail pp_element.get("ez", ez); pp_element.get("bz", bz); - m_lattice.emplace_back( ChrAcc(ds, ez, bz, a["dx"], a["dy"], a["rotation_degree"], nslice) ); + m_lattice.emplace_back( ChrAcc(ds, ez, bz, a["dx"], a["dy"], a["rotation_degree"], nslice, element_name) ); } else if (element_type == "thin_dipole") { auto a = detail::query_alignment(pp_element); @@ -354,7 +354,7 @@ namespace detail pp_element.get("theta", theta); pp_element.get("rc", rc); - m_lattice.emplace_back( ThinDipole(theta, rc, a["dx"], a["dy"], a["rotation_degree"]) ); + m_lattice.emplace_back( ThinDipole(theta, rc, a["dx"], a["dy"], a["rotation_degree"], element_name) ); } else if (element_type == "kicker") { auto a = detail::query_alignment(pp_element); @@ -370,7 +370,7 @@ namespace detail Kicker::UnitSystem::dimensionless : Kicker::UnitSystem::Tm; - m_lattice.emplace_back( Kicker(xkick, ykick, units, a["dx"], a["dy"], a["rotation_degree"]) ); + m_lattice.emplace_back( Kicker(xkick, ykick, units, a["dx"], a["dy"], a["rotation_degree"], element_name) ); } else if (element_type == "aperture") { auto a = detail::query_alignment(pp_element); @@ -386,7 +386,7 @@ namespace detail Aperture::Shape::rectangular : Aperture::Shape::elliptical; - m_lattice.emplace_back( Aperture(xmax, ymax, shape, a["dx"], a["dy"], a["rotation_degree"]) ); + m_lattice.emplace_back( Aperture(xmax, ymax, shape, a["dx"], a["dy"], a["rotation_degree"], element_name) ); } else if (element_type == "beam_monitor") { std::string openpmd_name = element_name; @@ -395,6 +395,8 @@ namespace detail pp_element.queryAdd("backend", openpmd_backend); std::string openpmd_encoding{"g"}; pp_element.queryAdd("encoding", openpmd_encoding); + int period_sample_intervals = 1; + pp_element.queryAdd("period_sample_intervals", period_sample_intervals); // optional: add and calculate additional particle properties // property: nonlinear lens invariants @@ -412,7 +414,7 @@ namespace detail pp_element.queryAdd("cn", cn); } - m_lattice.emplace_back(diagnostics::BeamMonitor(openpmd_name, openpmd_backend, openpmd_encoding)); + m_lattice.emplace_back(diagnostics::BeamMonitor(openpmd_name, openpmd_backend, openpmd_encoding, period_sample_intervals)); } else if (element_type == "line") { // Parse the lattice elements for the sub-lattice in the line diff --git a/src/main.cpp b/src/main.cpp index 4d6aeb315..aa3c973f8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -28,16 +28,16 @@ int main(int argc, char* argv[]) // it here so users can pass command line arguments impactx::initialization::default_init_AMReX(argc, argv); - BL_PROFILE_VAR("main()", pmain); { + BL_PROFILE_VAR("main()", pmain); impactx::ImpactX impactX; impactX.init_grids(); impactX.initBeamDistributionFromInputs(); impactX.initLatticeElementsFromInputs(); impactX.evolve(); + BL_PROFILE_VAR_STOP(pmain); impactX.finalize(); } - BL_PROFILE_VAR_STOP(pmain); #if defined(AMREX_USE_MPI) AMREX_ALWAYS_ASSERT(MPI_SUCCESS == MPI_Finalize()); diff --git a/src/particles/Push.H b/src/particles/Push.H index 7665df7a6..268a65e28 100644 --- a/src/particles/Push.H +++ b/src/particles/Push.H @@ -23,10 +23,14 @@ namespace impactx * @param[inout] pc container of the particles to push * @param[inout] element_variant a single element to push the particles through * @param[in] step global step for diagnostics + * @param[in] period for periodic lattices, this is the current period (turn or cycle) */ - void Push (ImpactXParticleContainer & pc, - KnownElements & element_variant, - int step); + void Push ( + ImpactXParticleContainer & pc, + KnownElements & element_variant, + int step, + int period + ); } // namespace impactx diff --git a/src/particles/Push.cpp b/src/particles/Push.cpp index 5769dd5d8..54f8cd8b4 100644 --- a/src/particles/Push.cpp +++ b/src/particles/Push.cpp @@ -16,17 +16,20 @@ namespace impactx { - void Push (ImpactXParticleContainer & pc, - KnownElements & element_variant, - int step) + void Push ( + ImpactXParticleContainer & pc, + KnownElements & element_variant, + int step, + int period + ) { // here we just access the element by its respective type - std::visit([&pc, step](auto&& element) + std::visit([&pc, step, period](auto&& element) { BL_PROFILE("impactx::Push"); // push reference particle & all particles - element(pc, step); + element(pc, step, period); }, element_variant); } diff --git a/src/particles/PushAll.H b/src/particles/PushAll.H index c5da15fa7..3fec0f3bc 100644 --- a/src/particles/PushAll.H +++ b/src/particles/PushAll.H @@ -26,6 +26,7 @@ namespace impactx * @param[in,out] pc particle container to push * @param[in,out] element the beamline element * @param[in] step global step for diagnostics + * @param[in] period for periodic lattices, this is the current period (turn or cycle) * @param[in] omp_parallel allow threading via OpenMP for the particle iterator loop (note: if OMP backend is active) */ template @@ -33,11 +34,12 @@ namespace impactx ImpactXParticleContainer & pc, T_Element & element, [[maybe_unused]] int step, + [[maybe_unused]] int period, [[maybe_unused]] bool omp_parallel = true ) { // performance profiling per element - std::string const profile_name = "impactx::Push::" + std::string(T_Element::name); + std::string const profile_name = "impactx::Push::" + std::string(T_Element::type); BL_PROFILE(profile_name); // preparing to access reference particle data: RefPart diff --git a/src/particles/ReferenceParticle.H b/src/particles/ReferenceParticle.H index 7f1b7ae19..226025b74 100644 --- a/src/particles/ReferenceParticle.H +++ b/src/particles/ReferenceParticle.H @@ -66,7 +66,7 @@ namespace impactx using namespace amrex::literals; amrex::ParticleReal const ref_gamma = -pt; - amrex::ParticleReal const ref_beta = sqrt(1.0_prt - 1.0_prt/pow(ref_gamma,2)); + amrex::ParticleReal const ref_beta = std::sqrt(1.0_prt - 1.0_prt/pow(ref_gamma,2)); return ref_beta; } @@ -81,7 +81,7 @@ namespace impactx using namespace amrex::literals; amrex::ParticleReal const ref_gamma = -pt; - amrex::ParticleReal const ref_betagamma = sqrt(pow(ref_gamma, 2) - 1.0_prt); + amrex::ParticleReal const ref_betagamma = std::sqrt(std::pow(ref_gamma, 2) - 1.0_prt); return ref_betagamma; } @@ -118,7 +118,7 @@ namespace impactx if (pt != 0.0_prt) { pt = -kin_energy_MeV() / massE - 1.0_prt; - pz = sqrt(pow(pt, 2) - 1.0_prt); + pz = std::sqrt(std::pow(pt, 2) - 1.0_prt); } return *this; @@ -155,7 +155,7 @@ namespace impactx px = 0.0; py = 0.0; pt = -kin_energy / mass_MeV() - 1.0_prt; - pz = sqrt(pow(pt, 2) - 1.0_prt); + pz = std::sqrt(std::pow(pt, 2) - 1.0_prt); return *this; } @@ -171,7 +171,7 @@ namespace impactx using namespace amrex::literals; amrex::ParticleReal const ref_gamma = -pt; - amrex::ParticleReal const ref_betagamma = sqrt(pow(ref_gamma, 2) - 1.0_prt); + amrex::ParticleReal const ref_betagamma = std::sqrt(std::pow(ref_gamma, 2) - 1.0_prt); //amrex::ParticleReal const ref_rigidity = mass*ref_betagamma*(ablastr::constant::SI::c)/charge; //fails due to "charge" amrex::ParticleReal const ref_rigidity = mass*ref_betagamma*(ablastr::constant::SI::c)/(ablastr::constant::SI::q_e); return ref_rigidity; diff --git a/src/particles/diagnostics/CMakeLists.txt b/src/particles/diagnostics/CMakeLists.txt index 1ff032aad..50ad97560 100644 --- a/src/particles/diagnostics/CMakeLists.txt +++ b/src/particles/diagnostics/CMakeLists.txt @@ -2,4 +2,5 @@ target_sources(lib PRIVATE ReducedBeamCharacteristics.cpp DiagnosticOutput.cpp + EmittanceInvariants.cpp ) diff --git a/src/particles/diagnostics/CovarianceMatrixMath.H b/src/particles/diagnostics/CovarianceMatrixMath.H new file mode 100644 index 000000000..a3b0099cf --- /dev/null +++ b/src/particles/diagnostics/CovarianceMatrixMath.H @@ -0,0 +1,219 @@ +/* Copyright 2022-2023 The Regents of the University of California, through Lawrence + * Berkeley National Laboratory (subject to receipt of any required + * approvals from the U.S. Dept. of Energy). All rights reserved. + * + * This file is part of ImpactX. + * + * Authors: Chad Mitchell, Axel Huebl + * License: BSD-3-Clause-LBNL + */ +#ifndef COVARIANCE_MATRIX_MATH_H +#define COVARIANCE_MATRIX_MATH_H + +#include +#include + +#include +#include +#include + +#include +#include + + +namespace impactx::diagnostics +{ + + /** Function to return the roots of a cubic polynomial ax^3 + bx^2 + cx + d. + * The trigonometric form of Cardano's formula is used. + * This implementation expects three real roots, which is verified + * by checking the sign of the discriminant. + * + * @param[in] a coefficient of cubic term + * @param[in] b coefficient of quadratic term + * @param[in] c coefficient of linear term + * @param[in] d coefficient of constant term + * @returns tuple of three real roots + */ + std::tuple< + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal + > + CubicRootsTrig ( + amrex::ParticleReal a, + amrex::ParticleReal b, + amrex::ParticleReal c, + amrex::ParticleReal d + ) + { + using namespace amrex::literals; + using ablastr::constant::math::pi; + + std::tuple roots; + amrex::ParticleReal x1 = 0.0_prt; + amrex::ParticleReal x2 = 0.0_prt; + amrex::ParticleReal x3 = 0.0_prt; + + amrex::ParticleReal Q = (3.0_prt*a*c - std::pow(b,2))/(9.0_prt * std::pow(a,2)); + amrex::ParticleReal R = (9.0_prt*a*b*c - 27_prt*std::pow(a,2)*d - 2.0_prt*std::pow(b,3))/(54.0_prt*std::pow(a,3)); + amrex::ParticleReal discriminant = std::pow(Q,3) + std::pow(R,2); + + // Discriminant should be < 0. Otherwise, keep theta at default and throw an error. + amrex::ParticleReal tol = 1.0e-12; //allow for roundoff error + if (discriminant > tol) { + + ablastr::warn_manager::WMRecordWarning( + "Impactx::diagnostics::CubicRootsTrig", + "Polynomial appearing in CubicRootsTrig has one or more complex " + "(non-real) roots. Only the real part is returned. This " + "suggests a loss of numerical precision in computation of the " + "eigenemittances. Treat eigenemittance values with caution.", + ablastr::warn_manager::WarnPriority::medium + ); + + std::cout << "Polynomial in CubicRoots has one or more complex roots." << "\n"; + + } else if (Q == 0.0_prt) { // Special case of a triple root + + x1 = - b/(3.0_prt*a); + x2 = - b/(3.0_prt*a); + x3 = - b/(3.0_prt*a); + + } else { + + //Three real roots in trigonometric form. + amrex::ParticleReal theta = std::acos(R/std::sqrt(-std::pow(Q,3))); + x1 = 2.0_prt*std::sqrt(-Q)*std::cos(theta/3.0_prt) - b/(3.0_prt*a); + x2 = 2.0_prt*std::sqrt(-Q)*std::cos(theta/3.0_prt + 2.0_prt*pi/3.0_prt) - b/(3.0_prt*a); + x3 = 2.0_prt*std::sqrt(-Q)*std::cos(theta/3.0_prt + 4.0_prt*pi/3.0_prt) - b/(3.0_prt*a); + + } + + roots = std::make_tuple(x1,x2,x3); + return roots; + } + + + /** Function to return the roots of a cubic polynomial ax^3 + bx^2 + cx + d. + * The algebraic form of Cardano's formula is used. + * This implementation expects three real roots, which is verified + * by checking the sign of the discriminant. + * + * @param[in] a coefficient of cubic term + * @param[in] b coefficient of quadratic term + * @param[in] c coefficient of linear term + * @param[in] d coefficient of constant term + * @returns tuple of three real roots + */ + std::tuple< + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal + > + CubicRootsAlg ( + amrex::ParticleReal a, + amrex::ParticleReal b, + amrex::ParticleReal c, + amrex::ParticleReal d + ) + { + using namespace amrex::literals; + using Complex = amrex::GpuComplex; + + std::tuple roots; + amrex::ParticleReal x1 = 0.0_prt; + amrex::ParticleReal x2 = 0.0_prt; + amrex::ParticleReal x3 = 0.0_prt; + + amrex::ParticleReal Q = (3.0_prt*a*c - std::pow(b,2))/(9.0_prt * std::pow(a,2)); + amrex::ParticleReal R = (9.0_prt*a*b*c - 27_prt*std::pow(a,2)*d - 2.0_prt*std::pow(b,3))/(54.0_prt*std::pow(a,3)); + amrex::ParticleReal discriminant = std::pow(Q,3) + std::pow(R,2); + + // Define complex variable C + Complex Qc(Q,0.0_prt); + Complex Rc(R,0.0_prt); + Complex Dc(discriminant,0.0_prt); + Complex C = amrex::pow(-Rc + amrex::sqrt(Dc),1.0/3.0); + + // Define a cubic root of unity xi + amrex::ParticleReal xire = -1.0/2.0; + amrex::ParticleReal xiim = std::sqrt(3.0)/2.0; + Complex xi(xire,xiim); + + //Three roots in algebraic form. + + if (C.m_real == 0.0_prt && C.m_imag == 0.0_prt) { // Special case of a triple root + + x1 = - b/(3.0_prt*a); + x2 = - b/(3.0_prt*a); + x3 = - b/(3.0_prt*a); + + } else { + + Complex z1 = Qc/C - C; + Complex z2 = Qc/(xi*C) - xi*C; + Complex z3 = Qc/(amrex::pow(xi,2)*C) - amrex::pow(xi,2)*C; + x1 = z2.m_real - b/(3.0*a); + x2 = z1.m_real - b/(3.0*a); + x3 = z3.m_real - b/(3.0*a); + } + + roots = std::make_tuple(x1,x2,x3); + return roots; + } + + + /** Function to take the trace of a square 6x6 matrix. + * + * @param[in] A a square matrix + * @returns the trace of A + */ + amrex::ParticleReal + TraceMat ( + amrex::Array2D const & A + ) + { + int const dim = 6; + amrex::ParticleReal trA = 0.0; + + for (int i = 1; i < dim+1; i++) { + trA += A(i,i); + } + return trA; + } + + + /** Function to multiply two square matrices of dimension 6. + * + * @param[in] A a square matrix + * @param[in] B square matrix + * @returns the matrix C = AB + */ + amrex::Array2D + MultiplyMat ( + amrex::Array2D const & A, + amrex::Array2D const & B + ) + { + amrex::Array2D C; + int const dim = 6; + + for (int i = 1; i < dim+1; i++) { + for (int j = 1; j < dim+1; j++) { + C(i,j) = 0; + + for (int k = 1; k < dim+1; k++) { + C(i,j) += A(i,k) * B(k,j); + } + + } + + } + return C; + } + + +} // namespace impactx::diagnostics + +#endif // COVARIANCE_MATRIX_MATH_H diff --git a/src/particles/diagnostics/DiagnosticOutput.cpp b/src/particles/diagnostics/DiagnosticOutput.cpp index fee8af6eb..1ec35610f 100644 --- a/src/particles/diagnostics/DiagnosticOutput.cpp +++ b/src/particles/diagnostics/DiagnosticOutput.cpp @@ -43,7 +43,33 @@ namespace impactx::diagnostics if (otype == OutputType::PrintRefParticle) { file_handler << "step s beta gamma beta_gamma x y z t px py pz pt\n"; } else if (otype == OutputType::PrintReducedBeamCharacteristics) { - file_handler << "step" << " " << "s" << " " + + // determine whether to output eigenemittances + amrex::ParmParse pp_diag("diag"); + bool compute_eigenemittances = false; + pp_diag.queryAdd("eigenemittances", compute_eigenemittances); + + if (compute_eigenemittances) { + file_handler << "step" << " " << "s" << " " + << "x_mean" << " " << "x_min" << " " << "x_max" << " " + << "y_mean" << " " << "y_min" << " " << "y_max" << " " + << "t_mean" << " " << "t_min" << " " << "t_max" << " " + << "sig_x" << " " << "sig_y" << " " << "sig_t" << " " + << "px_mean" << " " << "px_min" << " " << "px_max" << " " + << "py_mean" << " " << "py_min" << " " << "py_max" << " " + << "pt_mean" << " " << "pt_min" << " " << "pt_max" << " " + << "sig_px" << " " << "sig_py" << " " << "sig_pt" << " " + << "emittance_x" << " " << "emittance_y" << " " << "emittance_t" << " " + << "alpha_x" << " " << "alpha_y" << " " << "alpha_t" << " " + << "beta_x" << " " << "beta_y" << " " << "beta_t" << " " + << "dispersion_x" << " " << "dispersion_px" << " " + << "dispersion_y" << " " << "dispersion_py" << " " + << "emittance_xn" << " " << "emittance_yn" << " " << "emittance_tn" << " " + << "emittance_1" << " " << "emittance_2" << " " << "emittance_3" << " " + << "charge_C" << " " + << "\n"; + } else { + file_handler << "step" << " " << "s" << " " << "x_mean" << " " << "x_min" << " " << "x_max" << " " << "y_mean" << " " << "y_min" << " " << "y_max" << " " << "t_mean" << " " << "t_min" << " " << "t_max" << " " @@ -57,8 +83,10 @@ namespace impactx::diagnostics << "beta_x" << " " << "beta_y" << " " << "beta_t" << " " << "dispersion_x" << " " << "dispersion_px" << " " << "dispersion_y" << " " << "dispersion_py" << " " + << "emittance_xn" << " " << "emittance_yn" << " " << "emittance_tn" << " " << "charge_C" << " " << "\n"; + } } } @@ -92,7 +120,31 @@ namespace impactx::diagnostics amrex::ParticleReal const s = pc.GetRefParticle().s; - file_handler << step << " " << s << " " + // determine whether to output eigenemittances + amrex::ParmParse pp_diag("diag"); + bool compute_eigenemittances = false; + pp_diag.queryAdd("eigenemittances", compute_eigenemittances); + + if (compute_eigenemittances) { + file_handler << step << " " << s << " " + << rbc.at("x_mean") << " " << rbc.at("x_min") << " " << rbc.at("x_max") << " " + << rbc.at("y_mean") << " " << rbc.at("y_min") << " " << rbc.at("y_max") << " " + << rbc.at("t_mean") << " " << rbc.at("t_min") << " " << rbc.at("t_max") << " " + << rbc.at("sig_x") << " " << rbc.at("sig_y") << " " << rbc.at("sig_t") << " " + << rbc.at("px_mean") << " " << rbc.at("px_min") << " " << rbc.at("px_max") << " " + << rbc.at("py_mean") << " " << rbc.at("py_min") << " " << rbc.at("py_max") << " " + << rbc.at("pt_mean") << " " << rbc.at("pt_min") << " " << rbc.at("pt_max") << " " + << rbc.at("sig_px") << " " << rbc.at("sig_py") << " " << rbc.at("sig_pt") << " " + << rbc.at("emittance_x") << " " << rbc.at("emittance_y") << " " << rbc.at("emittance_t") << " " + << rbc.at("alpha_x") << " " << rbc.at("alpha_y") << " " << rbc.at("alpha_t") << " " + << rbc.at("beta_x") << " " << rbc.at("beta_y") << " " << rbc.at("beta_t") << " " + << rbc.at("dispersion_x") << " " << rbc.at("dispersion_px") << " " + << rbc.at("dispersion_y") << " " << rbc.at("dispersion_py") << " " + << rbc.at("emittance_xn") << " " << rbc.at("emittance_yn") << " " << rbc.at("emittance_tn") << " " + << rbc.at("emittance_1") << " " << rbc.at("emittance_2") << " " << rbc.at("emittance_3") << " " + << rbc.at("charge_C") << "\n"; + } else { + file_handler << step << " " << s << " " << rbc.at("x_mean") << " " << rbc.at("x_min") << " " << rbc.at("x_max") << " " << rbc.at("y_mean") << " " << rbc.at("y_min") << " " << rbc.at("y_max") << " " << rbc.at("t_mean") << " " << rbc.at("t_min") << " " << rbc.at("t_max") << " " @@ -106,7 +158,9 @@ namespace impactx::diagnostics << rbc.at("beta_x") << " " << rbc.at("beta_y") << " " << rbc.at("beta_t") << " " << rbc.at("dispersion_x") << " " << rbc.at("dispersion_px") << " " << rbc.at("dispersion_y") << " " << rbc.at("dispersion_py") << " " + << rbc.at("emittance_xn") << " " << rbc.at("emittance_yn") << " " << rbc.at("emittance_tn") << " " << rbc.at("charge_C") << "\n"; + } } // if( otype == OutputType::PrintReducedBeamCharacteristics) // TODO: add as an option to the monitor element diff --git a/src/particles/diagnostics/EmittanceInvariants.H b/src/particles/diagnostics/EmittanceInvariants.H new file mode 100644 index 000000000..177371953 --- /dev/null +++ b/src/particles/diagnostics/EmittanceInvariants.H @@ -0,0 +1,77 @@ +/* Copyright 2022-2023 The Regents of the University of California, through Lawrence + * Berkeley National Laboratory (subject to receipt of any required + * approvals from the U.S. Dept. of Energy). All rights reserved. + * + * This file is part of ImpactX. + * + * Authors: Marco Garten, Chad Mitchell, Axel Huebl + * License: BSD-3-Clause-LBNL + */ +#ifndef IMPACTX_EMITTANCE_INVARIANTS +#define IMPACTX_EMITTANCE_INVARIANTS + +#include "particles/ImpactXParticleContainer.H" + +#include +#include + +#include + + + +namespace impactx::diagnostics +{ + + /** Returns the three independent kinematic moment invariants + * denoted I2, I4, and I6 as constructed from the 6x6 + * beam covariance matrix. These three quantities are invariant + * under any linear symplectic transport map, and are used in the + * calculation of the three eigenemittances, as described in: + * + * G. Rangarajan, F. Neri, and A. Dragt, "Generalized Emittance + * Invariants," in Proc. 1989 IEEE Part. Accel. Conf., Chicago, IL, + * 1989, doi:10.1109/PAC.1989.73422. + * A. Dragt, F. Neri, and G. Rangarajan, "General Moment Invariants + * for Linear Hamiltonian Systems," Phys. Rev. A 45, 2572-2585 (1992), + * doi:10.1103/PhysRevA.45.2572. + * + * @param[in] Sigma symmetric 6x6 covariance matrix + * @returns tuple containing invariants I2, I4, and I6 + */ + std::tuple< + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal + > + KineticInvariants ( + amrex::Array2D const & Sigma + ); + + /** Returns the three eigenemittances + * denoted e1, e2, and e3 as constructed from the 6x6 + * beam covariance matrix. These three quantities are invariant + * under any linear symplectic transport map, and reduce to + * the projected normalized rms emittances in the limit of + * uncoupled transport. These quantities are described in: + * + * G. Rangarajan, F. Neri, and A. Dragt, "Generalized Emittance + * Invariants," in Proc. 1989 IEEE Part. Accel. Conf., Chicago, IL, + * 1989, doi:10.1109/PAC.1989.73422. + * A. Dragt, F. Neri, and G. Rangarajan, "General Moment Invariants + * for Linear Hamiltonian Systems," Phys. Rev. A 45, 2572-2585 (1992), + * doi:10.1103/PhysRevA.45.2572. + * + * @param[in] Sigma symmetric 6x6 covariance matrix + * @returns tuple containing eigenemittances e1, e2, and e3 + */ + std::tuple< + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal> + Eigenemittances ( + amrex::Array2D const & Sigma + ); + +} // namespace impactx::diagnostics + +#endif // IMPACTX_EMITTANCE_INVARIANTS diff --git a/src/particles/diagnostics/EmittanceInvariants.cpp b/src/particles/diagnostics/EmittanceInvariants.cpp new file mode 100644 index 000000000..20c6df32a --- /dev/null +++ b/src/particles/diagnostics/EmittanceInvariants.cpp @@ -0,0 +1,146 @@ +/* Copyright 2022-2023 The Regents of the University of California, through Lawrence + * Berkeley National Laboratory (subject to receipt of any required + * approvals from the U.S. Dept. of Energy). All rights reserved. + * + * This file is part of ImpactX. + * + * Authors: Chad Mitchell, Axel Huebl + * License: BSD-3-Clause-LBNL + */ +#include "CovarianceMatrixMath.H" + +#include +#include +#include +#include + +#include +#include +#include +#include + + +namespace impactx::diagnostics +{ + + /** This function returns the three independent kinetic invariants + * denoted I2, I4, and I6 as constructed from the 6x6 + * beam covariance matrix. These three quantities are invariant + * under any linear symplectic transport map, and are used in the + * calculation of the three eigenemittances. + * + * input - Sigma symmetric 6x6 covariance matrix + * returns - tuple containing invarants I2, I4, and I6 + */ + std::tuple< + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal + > + KineticInvariants ( + amrex::Array2D const & Sigma + ) + { + using namespace amrex::literals; + + std::tuple invariants; + amrex::ParticleReal I2 = 0.0_prt; + amrex::ParticleReal I4 = 0.0_prt; + amrex::ParticleReal I6 = 0.0_prt; + + // Intermediate matrices used for storage. + amrex::Array2D S1; + amrex::Array2D S2; + amrex::Array2D S4; + amrex::Array2D S6; + + // Construct the matrix S1 = Sigma*J. This is a + // permutation of the columns of Sigma with + // a change of sign. + for (int i = 1; i < 7; i++) { + for (int j = 1; j < 7; j++) { + if (j % 2 != 0) { + S1(i,j) = -Sigma(i,j+1); // if j is odd + } + else { + S1(i,j) = +Sigma(i,j-1); // if j is even + } + } + } + + // Carry out necessary matrix multiplications (3 are needed). + S2 = impactx::diagnostics::MultiplyMat(S1,S1); + S4 = impactx::diagnostics::MultiplyMat(S2,S2); + S6 = impactx::diagnostics::MultiplyMat(S2,S4); + + // Define the three kinematic invariants (should be nonnegative). + I2 = -impactx::diagnostics::TraceMat(S2)/2.0_prt; + I4 = +impactx::diagnostics::TraceMat(S4)/2.0_prt; + I6 = -impactx::diagnostics::TraceMat(S6)/2.0_prt; + + + invariants = std::make_tuple(I2,I4,I6); + return invariants; + } + + + /** This function returns the three eigenemittances + * denoted e1, e2, and e3 as constructed from the 6x6 + * beam covariance matrix. These three quantities are invariant + * under any linear symplectic transport map, and reduce to + * the projected normalized rms emittances in the limit of + * uncoupled transport. + * + * input - Sigma symmetric 6x6 covariance matrix + * returns - tuple containing eigenemittances e1, e2, and e3 + */ + std::tuple< + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal> + Eigenemittances ( + amrex::Array2D const & Sigma + ) + { + BL_PROFILE("impactx::diagnostics::Eigenemittances"); + + using namespace amrex::literals; + + std::tuple invariants; + std::tuple roots; + std::tuple emittances; + + // Get the invariants I2, I4, and I6 from the covariance matrix. + invariants = KineticInvariants(Sigma); + amrex::ParticleReal I2 = std::get<0>(invariants); + amrex::ParticleReal I4 = std::get<1>(invariants); + amrex::ParticleReal I6 = std::get<2>(invariants); + + // Construct the coefficients of the cubic polynomial. + // This expression for the characteristic polynomial can be found in: + // V. Balandin, W. Decking, and N. Golubeva, "Relations Between Projected + // Emittances and Eigenemittances," in IPAC2013, Shanghai, China, 2013, + // doi:10.48550/arXiv.1305.1532. + amrex::ParticleReal a = 1.0_prt; + amrex::ParticleReal b = -I2; + amrex::ParticleReal c = (pow(I2,2)-I4)/2.0_prt; + amrex::ParticleReal d = -pow(I2,3)/6.0_prt + I2*I4/2.0_prt - I6/3.0_prt; + + // Return the cubic coefficients + //std::cout << "Return a,b,c,d " << a << " " << b << " " << c << " " << d << "\n"; + + // Solve for the roots to obtain the eigenemittances. + // Caution: The order of e1,e2,e3 should be consistent with the + // order ex,ey,et in the limit of uncoupled transport. + // The order below is important and has been checked. + roots = CubicRootsTrig(a,b,c,d); + amrex::ParticleReal e1 = sqrt(std::abs(std::get<1>(roots))); + amrex::ParticleReal e2 = sqrt(std::abs(std::get<2>(roots))); + amrex::ParticleReal e3 = sqrt(std::abs(std::get<0>(roots))); + + emittances = std::make_tuple(e1,e2,e3); + return emittances; + } + + +} // namespace impactx::diagnostics diff --git a/src/particles/diagnostics/ReducedBeamCharacteristics.cpp b/src/particles/diagnostics/ReducedBeamCharacteristics.cpp index 0b6ed024d..99a11560d 100644 --- a/src/particles/diagnostics/ReducedBeamCharacteristics.cpp +++ b/src/particles/diagnostics/ReducedBeamCharacteristics.cpp @@ -12,6 +12,7 @@ #include "particles/ImpactXParticleContainer.H" #include "particles/ReferenceParticle.H" +#include "EmittanceInvariants.H" #include // for TinyProfiler #include // for AMREX_GPU_DEVICE @@ -33,6 +34,9 @@ namespace impactx::diagnostics RefPart const ref_part = pc.GetRefParticle(); // reference particle charge in C amrex::ParticleReal const q_C = ref_part.charge; + // reference particle relativistic beta*gamma + amrex::ParticleReal const bg = ref_part.beta_gamma(); + amrex::ParticleReal const bg2 = bg*bg; // preparing access to particle data: SoA using PType = typename ImpactXParticleContainer::SuperParticleType; @@ -151,7 +155,7 @@ namespace impactx::diagnostics * https://stackoverflow.com/questions/55136414/constexpr-variable-captured-inside-lambda-loses-its-constexpr-ness */ // number of reduction operations in second concurrent batch - static constexpr std::size_t num_red_ops_2 = 14; + static constexpr std::size_t num_red_ops_2 = 22; // prepare reduction operations for calculation of mean square and correlation values amrex::TypeMultiplier reduce_ops_2; using ReducedDataT2 = amrex::TypeMultiplier; @@ -182,12 +186,20 @@ namespace impactx::diagnostics const amrex::ParticleReal p_xpx = (p_x-x_mean)*(p_px-px_mean)*p_w; const amrex::ParticleReal p_ypy = (p_y-y_mean)*(p_py-py_mean)*p_w; const amrex::ParticleReal p_tpt = (p_t-t_mean)*(p_pt-pt_mean)*p_w; - // prepare correlations for dispersion + // prepare correlations for dispersion (4 required) const amrex::ParticleReal p_xpt = (p_x-x_mean)*(p_pt-pt_mean)*p_w; const amrex::ParticleReal p_pxpt = (p_px-px_mean)*(p_pt-pt_mean)*p_w; const amrex::ParticleReal p_ypt = (p_y-y_mean)*(p_pt-pt_mean)*p_w; const amrex::ParticleReal p_pypt = (p_py-py_mean)*(p_pt-pt_mean)*p_w; - + // prepare additional cross-plane correlations (8 required) + const amrex::ParticleReal p_xy = (p_x-x_mean)*(p_y-y_mean)*p_w; + const amrex::ParticleReal p_xpy = (p_x-x_mean)*(p_py-py_mean)*p_w; + const amrex::ParticleReal p_xt = (p_x-x_mean)*(p_t-t_mean)*p_w; + const amrex::ParticleReal p_pxy = (p_px-px_mean)*(p_y-y_mean)*p_w; + const amrex::ParticleReal p_pxpy = (p_px-px_mean)*(p_py-py_mean)*p_w; + const amrex::ParticleReal p_pxt = (p_px-px_mean)*(p_t-t_mean)*p_w; + const amrex::ParticleReal p_yt = (p_y-y_mean)*(p_t-t_mean)*p_w; + const amrex::ParticleReal p_pyt = (p_py-py_mean)*(p_t-t_mean)*p_w; const amrex::ParticleReal p_charge = q_C*p_w; @@ -195,6 +207,7 @@ namespace impactx::diagnostics p_px_ms, p_py_ms, p_pt_ms, p_xpx, p_ypy, p_tpt, p_xpt, p_pxpt, p_ypt, p_pypt, + p_xy, p_xpy, p_xt, p_pxy, p_pxpy, p_pxt, p_yt, p_pyt, p_charge}; }, reduce_ops_2 @@ -207,6 +220,7 @@ namespace impactx::diagnostics * px_ms, py_ms, pt_ms, * xpx, ypy, tpt, * p_xpt, p_pxpt, p_ypt, p_pypt, + * p_xy, p_xpy, p_xt, p_pxy, p_pxpy, p_pxt, p_yt, p_pyt, * charge */ amrex::constexpr_for<0, num_red_ops_2> ([&](auto i) { @@ -248,7 +262,15 @@ namespace impactx::diagnostics amrex::ParticleReal const pxpt = values_per_rank_2nd.at(10) /= w_sum; amrex::ParticleReal const ypt = values_per_rank_2nd.at(11) /= w_sum; amrex::ParticleReal const pypt = values_per_rank_2nd.at(12) /= w_sum; - amrex::ParticleReal const charge = values_per_rank_2nd.at(13); + amrex::ParticleReal const xy = values_per_rank_2nd.at(13) /= w_sum; + amrex::ParticleReal const xpy = values_per_rank_2nd.at(14) /= w_sum; + amrex::ParticleReal const xt = values_per_rank_2nd.at(15) /= w_sum; + amrex::ParticleReal const pxy = values_per_rank_2nd.at(16) /= w_sum; + amrex::ParticleReal const pxpy = values_per_rank_2nd.at(17) /= w_sum; + amrex::ParticleReal const pxt = values_per_rank_2nd.at(18) /= w_sum; + amrex::ParticleReal const yt = values_per_rank_2nd.at(19) /= w_sum; + amrex::ParticleReal const pyt = values_per_rank_2nd.at(20) /= w_sum; + amrex::ParticleReal const charge = values_per_rank_2nd.at(21); // standard deviations of positions amrex::ParticleReal const sig_x = std::sqrt(x_ms); amrex::ParticleReal const sig_y = std::sqrt(y_ms); @@ -262,10 +284,10 @@ namespace impactx::diagnostics amrex::ParticleReal const emittance_y = std::sqrt(y_ms*py_ms-ypy*ypy); amrex::ParticleReal const emittance_t = std::sqrt(t_ms*pt_ms-tpt*tpt); // Dispersion and dispersive beam moments - amrex::ParticleReal const dispersion_x = - xpt / pt_ms; - amrex::ParticleReal const dispersion_px = - pxpt / pt_ms; - amrex::ParticleReal const dispersion_y = - ypt / pt_ms; - amrex::ParticleReal const dispersion_py = - pypt / pt_ms; + amrex::ParticleReal const dispersion_x = ((pt_ms > 0.0) ? (- xpt / pt_ms) : 0.0); + amrex::ParticleReal const dispersion_px = ((pt_ms > 0.0) ? (- pxpt / pt_ms) : 0.0); + amrex::ParticleReal const dispersion_y = ((pt_ms > 0.0) ? (- ypt / pt_ms) : 0.0); + amrex::ParticleReal const dispersion_py = ((pt_ms > 0.0) ? (- pypt / pt_ms) : 0.0); amrex::ParticleReal const x_msd = x_ms - pt_ms*dispersion_x*dispersion_x; amrex::ParticleReal const px_msd = px_ms - pt_ms*dispersion_px*dispersion_px; amrex::ParticleReal const xpx_d = xpx - pt_ms*dispersion_x*dispersion_px; @@ -283,6 +305,65 @@ namespace impactx::diagnostics amrex::ParticleReal const alpha_y = - ypy_d / emittance_yd; amrex::ParticleReal const alpha_t = - tpt / emittance_t; + // Calculate normalized emittances + amrex::ParticleReal emittance_xn = emittance_x * bg; + amrex::ParticleReal emittance_yn = emittance_y * bg; + amrex::ParticleReal emittance_tn = emittance_t * bg; + + // Determine whether to calculate eigenemittances, and initialize + amrex::ParmParse pp_diag("diag"); + bool compute_eigenemittances = false; + pp_diag.queryAdd("eigenemittances", compute_eigenemittances); + amrex::ParticleReal emittance_1 = emittance_xn; + amrex::ParticleReal emittance_2 = emittance_yn; + amrex::ParticleReal emittance_3 = emittance_tn; + + if (compute_eigenemittances) { + // Store the covariance matrix in dynamical variables: + amrex::Array2D Sigma; + Sigma(1,1) = x_ms; + Sigma(1,2) = xpx * bg; + Sigma(1,3) = xy; + Sigma(1,4) = xpy * bg; + Sigma(1,5) = xt; + Sigma(1,6) = xpt * bg; + Sigma(2,1) = xpx * bg; + Sigma(2,2) = px_ms * bg2; + Sigma(2,3) = pxy * bg; + Sigma(2,4) = pxpy * bg2; + Sigma(2,5) = pxt * bg; + Sigma(2,6) = pxpt * bg2; + Sigma(3,1) = xy; + Sigma(3,2) = pxy * bg; + Sigma(3,3) = y_ms; + Sigma(3,4) = ypy * bg; + Sigma(3,5) = yt; + Sigma(3,6) = ypt * bg; + Sigma(4,1) = xpy * bg; + Sigma(4,2) = pxpy * bg2; + Sigma(4,3) = ypy * bg; + Sigma(4,4) = py_ms * bg2; + Sigma(4,5) = pyt * bg; + Sigma(4,6) = pypt * bg2; + Sigma(5,1) = xt; + Sigma(5,2) = pxt * bg; + Sigma(5,3) = yt; + Sigma(5,4) = pyt * bg; + Sigma(5,5) = t_ms; + Sigma(5,6) = tpt * bg; + Sigma(6,1) = xpt * bg; + Sigma(6,2) = pxpt * bg2; + Sigma(6,3) = ypt * bg; + Sigma(6,4) = pypt * bg2; + Sigma(6,5) = tpt * bg; + Sigma(6,6) = pt_ms * bg2; + // Calculate eigenemittances + std::tuple emittances = Eigenemittances(Sigma); + emittance_1 = std::get<0>(emittances); + emittance_2 = std::get<1>(emittances); + emittance_3 = std::get<2>(emittances); + } + std::unordered_map data; data["x_mean"] = x_mean; data["x_min"] = x_min; @@ -322,6 +403,14 @@ namespace impactx::diagnostics data["dispersion_y"] = dispersion_y; data["dispersion_py"] = dispersion_py; data["charge_C"] = charge; + data["emittance_xn"] = emittance_xn; + data["emittance_yn"] = emittance_yn; + data["emittance_tn"] = emittance_tn; + if (compute_eigenemittances) { + data["emittance_1"] = emittance_1; + data["emittance_2"] = emittance_2; + data["emittance_3"] = emittance_3; + } return data; } diff --git a/src/particles/distribution/Gaussian.H b/src/particles/distribution/Gaussian.H index d6baf9640..24397c0b0 100644 --- a/src/particles/distribution/Gaussian.H +++ b/src/particles/distribution/Gaussian.H @@ -100,34 +100,34 @@ namespace impactx::distribution u1 = amrex::Random(engine); u2 = amrex::Random(engine); - ln1 = sqrt(-2_prt*log(u1)); - x = ln1*cos(2_prt*pi*u2); - px = ln1*sin(2_prt*pi*u2); + ln1 = std::sqrt(-2_prt*std::log(u1)); + x = ln1 * std::cos(2_prt*pi*u2); + px = ln1 * std::sin(2_prt*pi*u2); u1 = amrex::Random(engine); u2 = amrex::Random(engine); - ln1 = sqrt(-2_prt*log(u1)); - y = ln1*cos(2_prt*pi*u2); - py = ln1*sin(2_prt*pi*u2); + ln1 = std::sqrt(-2_prt*std::log(u1)); + y = ln1 * std::cos(2_prt*pi*u2); + py = ln1 * std::sin(2_prt*pi*u2); u1 = amrex::Random(engine); u2 = amrex::Random(engine); - ln1 = sqrt(-2_prt*log(u1)); - t = ln1*cos(2_prt*pi*u2); - pt = ln1*sin(2_prt*pi*u2); + ln1 = std::sqrt(-2_prt*std::log(u1)); + t = ln1 * std::cos(2_prt*pi*u2); + pt = ln1 * std::sin(2_prt*pi*u2); // Transform to produce the desired second moments/correlations: - root = sqrt(1.0_prt-m_muxpx*m_muxpx); + root = std::sqrt(1.0_prt-m_muxpx*m_muxpx); a1 = m_lambdaX * x / root; a2 = m_lambdaPx * (-m_muxpx * x / root + px); x = a1; px = a2; - root = sqrt(1.0_prt-m_muypy*m_muypy); + root = std::sqrt(1.0_prt-m_muypy*m_muypy); a1 = m_lambdaY * y / root; a2 = m_lambdaPy * (-m_muypy * y / root + py); y = a1; py = a2; - root = sqrt(1.0_prt-m_mutpt*m_mutpt); + root = std::sqrt(1.0_prt-m_mutpt*m_mutpt); a1 = m_lambdaT * t / root; a2 = m_lambdaPt * (-m_mutpt * t / root + pt); t = a1; diff --git a/src/particles/distribution/KVdist.H b/src/particles/distribution/KVdist.H index 87e343399..3554013eb 100644 --- a/src/particles/distribution/KVdist.H +++ b/src/particles/distribution/KVdist.H @@ -101,27 +101,27 @@ namespace impactx::distribution v = amrex::Random(engine); phi = amrex::Random(engine); phi = 2_prt*pi*phi; - r = sqrt(v); - x = r*cos(phi); - y = r*sin(phi); + r = std::sqrt(v); + x = r * std::cos(phi); + y = r * std::sin(phi); // Sample and transform to define (px,py): beta = amrex::Random(engine); beta = 2_prt*pi*beta; - p = sqrt(1_prt-pow(r,2)); - px = p*cos(beta); - py = p*sin(beta); + p = std::sqrt(1_prt-pow(r,2)); + px = p * std::cos(beta); + py = p * std::sin(beta); // Sample and transform to define (t,pt): t = amrex::Random(engine); t = 2.0_prt*(t-0.5_prt); u1 = amrex::Random(engine); u2 = amrex::Random(engine); - ln1 = sqrt(-2_prt*log(u1)); - pt = ln1*cos(2_prt*pi*u2); + ln1 = std::sqrt(-2_prt*std::log(u1)); + pt = ln1 * std::cos(2_prt*pi*u2); // Scale to produce the identity covariance matrix: - amrex::ParticleReal const c = sqrt(3.0_prt); + amrex::ParticleReal const c = std::sqrt(3.0_prt); x = 2_prt*x; y = 2_prt*y; t = c*t; @@ -130,17 +130,17 @@ namespace impactx::distribution // pt = pt; // Transform to produce the desired second moments/correlations: - root = sqrt(1.0_prt-m_muxpx*m_muxpx); + root = std::sqrt(1.0_prt-m_muxpx*m_muxpx); a1 = m_lambdaX * x / root; a2 = m_lambdaPx * (-m_muxpx * x / root + px); x = a1; px = a2; - root = sqrt(1.0_prt-m_muypy*m_muypy); + root = std::sqrt(1.0_prt-m_muypy*m_muypy); a1 = m_lambdaY * y / root; a2 = m_lambdaPy * (-m_muypy * y / root + py); y = a1; py = a2; - root = sqrt(1.0_prt-m_mutpt*m_mutpt); + root = std::sqrt(1.0_prt-m_mutpt*m_mutpt); a1 = m_lambdaT * t / root; a2 = m_lambdaPt * (-m_mutpt * t / root + pt); t = a1; diff --git a/src/particles/distribution/Kurth4D.H b/src/particles/distribution/Kurth4D.H index cd254d2f9..d5b40e2d7 100644 --- a/src/particles/distribution/Kurth4D.H +++ b/src/particles/distribution/Kurth4D.H @@ -102,9 +102,9 @@ namespace impactx::distribution v = amrex::Random(engine); phi = amrex::Random(engine); phi = 2_prt*pi*phi; - r = sqrt(v); - x = r*cos(phi); - y = r*sin(phi); + r = std::sqrt(v); + x = r * std::cos(phi); + y = r * std::sin(phi); // Random samples used to define Lz: u = amrex::Random(engine); @@ -113,25 +113,25 @@ namespace impactx::distribution // Random samples used to define pr: alpha = amrex::Random(engine); alpha = pi*alpha; - pmax = 1.0_prt - pow((Lz/r),2) - pow(r,2) + pow(Lz,2); - pmax = sqrt(pmax); - pr = pmax*cos(alpha); + pmax = 1.0_prt - std::pow((Lz/r),2) - std::pow(r,2) + std::pow(Lz,2); + pmax = std::sqrt(pmax); + pr = pmax * std::cos(alpha); pphi = Lz/r; // Transformations used to obtain (px,py): - px = pr*cos(phi)-pphi*sin(phi); - py = pr*sin(phi)+pphi*cos(phi); + px = pr * std::cos(phi)-pphi * std::sin(phi); + py = pr * std::sin(phi)+pphi * std::cos(phi); // Sample and transform to define (t,pt): t = amrex::Random(engine); t = 2.0_prt*(t-0.5_prt); u1 = amrex::Random(engine); u2 = amrex::Random(engine); - ln1 = sqrt(-2_prt*log(u1)); - pt = ln1*cos(2_prt*pi*u2); + ln1 = std::sqrt(-2_prt*std::log(u1)); + pt = ln1 * std::cos(2_prt*pi*u2); // Scale to produce the identity covariance matrix: - amrex::ParticleReal const c = sqrt(3.0_prt); + amrex::ParticleReal const c = std::sqrt(3.0_prt); x = 2_prt*x; y = 2_prt*y; t = c*t; @@ -140,17 +140,17 @@ namespace impactx::distribution // pt = pt; // Transform to produce the desired second moments/correlations: - root = sqrt(1.0_prt-m_muxpx*m_muxpx); + root = std::sqrt(1.0_prt-m_muxpx*m_muxpx); a1 = m_lambdaX * x / root; a2 = m_lambdaPx * (-m_muxpx * x / root + px); x = a1; px = a2; - root = sqrt(1.0_prt-m_muypy*m_muypy); + root = std::sqrt(1.0_prt-m_muypy*m_muypy); a1 = m_lambdaY * y / root; a2 = m_lambdaPy * (-m_muypy * y / root + py); y = a1; py = a2; - root = sqrt(1.0_prt-m_mutpt*m_mutpt); + root = std::sqrt(1.0_prt-m_mutpt*m_mutpt); a1 = m_lambdaT * t / root; a2 = m_lambdaPt * (-m_mutpt * t / root + pt); t = a1; diff --git a/src/particles/distribution/Kurth6D.H b/src/particles/distribution/Kurth6D.H index e3d1b8c42..b0a1da425 100644 --- a/src/particles/distribution/Kurth6D.H +++ b/src/particles/distribution/Kurth6D.H @@ -104,40 +104,40 @@ namespace impactx::distribution v = amrex::Random(engine); costheta = amrex::Random(engine); costheta = 2_prt*(costheta-0.5_prt); - sintheta = sqrt(1_prt-pow(costheta,2)); + sintheta = std::sqrt(1_prt-pow(costheta,2)); phi = amrex::Random(engine); phi = 2_prt*pi*phi; // Transformations for (x,y,t): - r = pow(v,1_prt/3_prt); - x = r*sintheta*cos(phi); - y = r*sintheta*sin(phi); + r = std::pow(v,1_prt/3_prt); + x = r*sintheta * std::cos(phi); + y = r*sintheta * std::sin(phi); t = r*costheta; // Random samples used to define L: L = amrex::Random(engine); - L = r*sqrt(L); + L = r*std::sqrt(L); // Random samples used to define pr: alpha = amrex::Random(engine); alpha = pi*alpha; - pmax = 1_prt - pow(L/r,2) - pow(r,2) + pow(L,2); - pmax = sqrt(pmax); - pr = pmax*cos(alpha); + pmax = 1_prt - std::pow(L/r,2) - std::pow(r,2) + std::pow(L,2); + pmax = std::sqrt(pmax); + pr = pmax * std::cos(alpha); // Random samples used to define ptangent: beta = amrex::Random(engine); beta = 2_prt*pi*beta; - p1 = L/r*cos(beta); // This is phi component - p2 = L/r*sin(beta); // This is theta component + p1 = L/r * std::cos(beta); // This is phi component + p2 = L/r * std::sin(beta); // This is theta component // Transformation from spherical to Cartesian coord.: - px = pr*sintheta*cos(phi) + p2*costheta*cos(phi) - p1*sin(phi); - py = pr*sintheta*sin(phi) + p2*costheta*sin(phi) + p1*cos(phi); + px = pr*sintheta * std::cos(phi) + p2*costheta * std::cos(phi) - p1 * std::sin(phi); + py = pr*sintheta * std::sin(phi) + p2*costheta * std::sin(phi) + p1 * std::cos(phi); pt = pr*costheta - p2*sintheta; // Scale to produce the identity covariance matrix: - amrex::ParticleReal const c = sqrt(5.0_prt); + amrex::ParticleReal const c = std::sqrt(5.0_prt); x = c*x; y = c*y; t = c*t; @@ -146,17 +146,17 @@ namespace impactx::distribution pt = c*pt; // Transform to produce the desired second moments/correlations: - root = sqrt(1.0_prt-m_muxpx*m_muxpx); + root = std::sqrt(1.0_prt-m_muxpx*m_muxpx); a1 = m_lambdaX * x / root; a2 = m_lambdaPx * (-m_muxpx * x / root + px); x = a1; px = a2; - root = sqrt(1.0_prt-m_muypy*m_muypy); + root = std::sqrt(1.0_prt-m_muypy*m_muypy); a1 = m_lambdaY * y / root; a2 = m_lambdaPy * (-m_muypy * y / root + py); y = a1; py = a2; - root = sqrt(1.0_prt-m_mutpt*m_mutpt); + root = std::sqrt(1.0_prt-m_mutpt*m_mutpt); a1 = m_lambdaT * t / root; a2 = m_lambdaPt * (-m_mutpt * t / root + pt); t = a1; diff --git a/src/particles/distribution/Semigaussian.H b/src/particles/distribution/Semigaussian.H index 63d985657..f6c3deaa1 100644 --- a/src/particles/distribution/Semigaussian.H +++ b/src/particles/distribution/Semigaussian.H @@ -103,14 +103,14 @@ namespace impactx::distribution phi = amrex::Random(engine); phi = 2_prt*pi*phi; v = amrex::Random(engine); - r = sqrt(v); - x = r*cos(phi); - y = r*sin(phi); + r = std::sqrt(v); + x = r * std::cos(phi); + y = r * std::sin(phi); t = amrex::Random(engine); t = 2_prt*(t-0.5_prt); // Scale to produce the identity covariance matrix: - amrex::ParticleReal const c = sqrt(3.0_prt); + amrex::ParticleReal const c = std::sqrt(3.0_prt); x = 2_prt*x; y = 2_prt*y; t = c*t; @@ -119,27 +119,27 @@ namespace impactx::distribution u1 = amrex::Random(engine); u2 = amrex::Random(engine); - ln1 = sqrt(-2_prt*log(u1)); - px = ln1*cos(2_prt*pi*u2); - py = ln1*sin(2_prt*pi*u2); + ln1 = std::sqrt(-2_prt*std::log(u1)); + px = ln1 * std::cos(2_prt*pi*u2); + py = ln1 * std::sin(2_prt*pi*u2); u1 = amrex::Random(engine); u2 = amrex::Random(engine); - ln1 = sqrt(-2_prt*log(u1)); - pt = ln1*cos(2_prt*pi*u2); + ln1 = std::sqrt(-2_prt*std::log(u1)); + pt = ln1 * std::cos(2_prt*pi*u2); // Transform to produce the desired second moments/correlations: - root = sqrt(1.0_prt-m_muxpx*m_muxpx); + root = std::sqrt(1.0_prt-m_muxpx*m_muxpx); a1 = m_lambdaX * x / root; a2 = m_lambdaPx * (-m_muxpx * x / root + px); x = a1; px = a2; - root = sqrt(1.0_prt-m_muypy*m_muypy); + root = std::sqrt(1.0_prt-m_muypy*m_muypy); a1 = m_lambdaY * y / root; a2 = m_lambdaPy * (-m_muypy * y / root + py); y = a1; py = a2; - root = sqrt(1.0_prt-m_mutpt*m_mutpt); + root = std::sqrt(1.0_prt-m_mutpt*m_mutpt); a1 = m_lambdaT * t / root; a2 = m_lambdaPt * (-m_mutpt * t / root + pt); t = a1; diff --git a/src/particles/distribution/Thermal.H b/src/particles/distribution/Thermal.H index 63169332a..898f86a4a 100644 --- a/src/particles/distribution/Thermal.H +++ b/src/particles/distribution/Thermal.H @@ -97,7 +97,7 @@ namespace distribution amrex::ParticleReal q_e = refpart.charge_qe(); // Set space charge intensity - m_Cintensity = q_e*bunch_charge/(pow(bg,2)*Erest*ablastr::constant::SI::ep0); + m_Cintensity = q_e*bunch_charge/(std::pow(bg,2)*Erest*ablastr::constant::SI::ep0); // Set minimum and maximum radius amrex::ParticleReal r_scale = matched_scale_radius(); @@ -106,8 +106,8 @@ namespace distribution // amrex::PrintToFile("equilibrium_params.out") << r_scale << " " << data.Cintensity << "\n"; // Scale the parameters p1 and p2 - amrex::ParticleReal rt2pi = sqrt(2.0_prt*pi); - amrex::ParticleReal p_scale = pow(r_scale*rt2pi,-3); + amrex::ParticleReal rt2pi = std::sqrt(2.0_prt*pi); + amrex::ParticleReal p_scale = std::pow(r_scale*rt2pi,-3); m_p1 = m_p1*p_scale; m_p2 = m_p2*p_scale; @@ -161,8 +161,8 @@ namespace distribution amrex::ParticleReal k = m_k; amrex::ParticleReal kT = (1.0_prt - m_w) * m_T1 + m_w * m_T2; - amrex::ParticleReal a = m_Cintensity/(4.0_prt*pi*5.0_prt*sqrt(5.0_prt)); - amrex::ParticleReal rscale = sqrt(kT + pow(a*k,2.0/3.0))/k; + amrex::ParticleReal a = m_Cintensity/(4.0_prt*pi*5.0_prt*std::sqrt(5.0_prt)); + amrex::ParticleReal rscale = std::sqrt(kT + std::pow(a*k,2.0/3.0))/k; return rscale; } @@ -252,17 +252,17 @@ namespace distribution // Define intermediate quantities amrex::ParticleReal potential = 0.0_prt; - potential = pow(k*r,2.0)/2.0_prt + c1*phi1 + c2*phi2; - amrex::ParticleReal Pdensity1 = m_p1*exp(-potential/T1); - amrex::ParticleReal Pdensity2 = m_p2*exp(-potential/T2); + potential = std::pow(k*r,2.0)/2.0_prt + c1*phi1 + c2*phi2; + amrex::ParticleReal Pdensity1 = m_p1 * std::exp(-potential/T1); + amrex::ParticleReal Pdensity2 = m_p2 * std::exp(-potential/T2); // amrex::ParticleReal Pdensity_tot = (1.0_prt-w)*Pdensity1 + w*Pdensity2; // amrex::PrintToFile("Pdensity.out") << reval << " " << Pdensity_tot << "\n"; // Apply map to update f1 and f2: m_phi1 = phi1; m_phi2 = phi2; - m_f1 = f1 + tau*4.0_prt*pi*pow(r,2.0)*Pdensity1; - m_f2 = f2 + tau*4.0_prt*pi*pow(r,2.0)*Pdensity2; + m_f1 = f1 + tau*4.0_prt*pi * std::pow(r,2.0)*Pdensity1; + m_f2 = f2 + tau*4.0_prt*pi * std::pow(r,2.0)*Pdensity2; reval = r; } }; @@ -371,28 +371,28 @@ namespace distribution // Generate six standard normal random variables using Box-Muller: u1 = amrex::Random(engine); u2 = amrex::Random(engine); - ln1 = sqrt(-2_prt*log(u1)); - g1 = ln1*cos(2_prt*pi*u2); - g2 = ln1*sin(2_prt*pi*u2); + ln1 = std::sqrt(-2_prt*std::log(u1)); + g1 = ln1 * std::cos(2_prt*pi*u2); + g2 = ln1 * std::sin(2_prt*pi*u2); u1 = amrex::Random(engine); u2 = amrex::Random(engine); - ln1 = sqrt(-2_prt*log(u1)); - g3 = ln1*cos(2_prt*pi*u2); - g4 = ln1*sin(2_prt*pi*u2); + ln1 = std::sqrt(-2_prt*std::log(u1)); + g3 = ln1 * std::cos(2_prt*pi*u2); + g4 = ln1 * std::sin(2_prt*pi*u2); u1 = amrex::Random(engine); u2 = amrex::Random(engine); - ln1 = sqrt(-2_prt*log(u1)); - g5 = ln1*cos(2_prt*pi*u2); - g6 = ln1*sin(2_prt*pi*u2); + ln1 = std::sqrt(-2_prt*std::log(u1)); + g5 = ln1 * std::cos(2_prt*pi*u2); + g6 = ln1 * std::sin(2_prt*pi*u2); // Scale the last three variables to produce the momenta: amrex::ParticleReal kT = (uhalo > m_w) ? m_T1 : m_T2; // select core or halo value - px = sqrt(kT)*g4; - py = sqrt(kT)*g5; - pz = sqrt(kT)*g6; + px = std::sqrt(kT)*g4; + py = std::sqrt(kT)*g5; + pz = std::sqrt(kT)*g6; // Normalize the first three variables to produce uniform samples on the unit 3-sphere: - norm = sqrt(g1*g1+g2*g2+g3*g3); + norm = std::sqrt(g1*g1+g2*g2+g3*g3); g1 /= norm; g2 /= norm; g3 /= norm; diff --git a/src/particles/distribution/Triangle.H b/src/particles/distribution/Triangle.H index a1f40dc14..631dd5338 100644 --- a/src/particles/distribution/Triangle.H +++ b/src/particles/distribution/Triangle.H @@ -99,29 +99,29 @@ namespace impactx::distribution // Sample the t coordinate for a ramped triangular profile (unit // variance): u0 = amrex::Random(engine); - t = sqrt(2_prt)*(2_prt-3_prt*sqrt(u0)); + t = std::sqrt(2_prt)*(2_prt-3_prt*std::sqrt(u0)); // Generate five standard normal random variables using Box-Muller: u1 = amrex::Random(engine); u2 = amrex::Random(engine); - ln1 = sqrt(-2_prt*log(u1)); - g1 = ln1*cos(2_prt*pi*u2); - g2 = ln1*sin(2_prt*pi*u2); + ln1 = std::sqrt(-2_prt*std::log(u1)); + g1 = ln1 * std::cos(2_prt*pi*u2); + g2 = ln1 * std::sin(2_prt*pi*u2); u1 = amrex::Random(engine); u2 = amrex::Random(engine); - ln1 = sqrt(-2_prt*log(u1)); - g3 = ln1*cos(2_prt*pi*u2); - g4 = ln1*sin(2_prt*pi*u2); + ln1 = std::sqrt(-2_prt*std::log(u1)); + g3 = ln1 * std::cos(2_prt*pi*u2); + g4 = ln1 * std::sin(2_prt*pi*u2); u1 = amrex::Random(engine); u2 = amrex::Random(engine); - ln1 = sqrt(-2_prt*log(u1)); - g5 = ln1*cos(2_prt*pi*u2); + ln1 = std::sqrt(-2_prt*std::log(u1)); + g5 = ln1 * std::cos(2_prt*pi*u2); // Use one of these normal random variables for pt: pt = g5; // Normalize the rest to produce uniform samples on the unit sphere: - norm = sqrt(g1*g1+g2*g2+g3*g3+g4*g4); + norm = std::sqrt(g1*g1+g2*g2+g3*g3+g4*g4); g1 /= norm; g2 /= norm; g3 /= norm; @@ -130,24 +130,24 @@ namespace impactx::distribution // Scale to produce uniform samples in a 4D ball (unit variance): d = 4_prt; // unit ball dimension u1 = amrex::Random(engine); // uniform sample - u2 = sqrt(d+2_prt)*pow(u1,1_prt/d); + u2 = std::sqrt(d+2_prt) * std::pow(u1,1_prt/d); x = g1*u2; y = g2*u2; px = g3*u2; py = g4*u2; // Transform to produce the desired second moments/correlations: - root = sqrt(1.0_prt-m_muxpx*m_muxpx); + root = std::sqrt(1.0_prt-m_muxpx*m_muxpx); a1 = m_lambdaX * x / root; a2 = m_lambdaPx * (-m_muxpx * x / root + px); x = a1; px = a2; - root = sqrt(1.0_prt-m_muypy*m_muypy); + root = std::sqrt(1.0_prt-m_muypy*m_muypy); a1 = m_lambdaY * y / root; a2 = m_lambdaPy * (-m_muypy * y / root + py); y = a1; py = a2; - root = sqrt(1.0_prt-m_mutpt*m_mutpt); + root = std::sqrt(1.0_prt-m_mutpt*m_mutpt); a1 = m_lambdaT * t / root; a2 = m_lambdaPt * (-m_mutpt * t / root + pt); t = a1; diff --git a/src/particles/elements/All.H b/src/particles/elements/All.H index 6f7f72066..ba108c9fe 100644 --- a/src/particles/elements/All.H +++ b/src/particles/elements/All.H @@ -23,6 +23,7 @@ #include "ExactDrift.H" #include "ExactSbend.H" #include "Kicker.H" +#include "Marker.H" #include "Multipole.H" #include "Empty.H" #include "NonlinearLens.H" @@ -60,6 +61,7 @@ namespace impactx ExactDrift, ExactSbend, Kicker, + Marker, Multipole, NonlinearLens, Programmable, diff --git a/src/particles/elements/Aperture.H b/src/particles/elements/Aperture.H index fa7d8bed7..9c286870e 100644 --- a/src/particles/elements/Aperture.H +++ b/src/particles/elements/Aperture.H @@ -15,6 +15,7 @@ #include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thin.H" +#include "mixin/named.H" #include "mixin/nofinalize.H" #include @@ -25,20 +26,25 @@ namespace impactx { struct Aperture - : public elements::BeamOptic, + : public elements::Named, + public elements::BeamOptic, public elements::Thin, public elements::Alignment, public elements::NoFinalize { - static constexpr auto name = "Aperture"; + static constexpr auto type = "Aperture"; using PType = ImpactXParticleContainer::ParticleType; + // TODO: make AMREX_ENUM and simplify @see shape_name implementation with it enum Shape { rectangular, elliptical }; + static std::string + shape_name (Shape const & shape); + /** A thin collimator element that applies a transverse aperture boundary. * Particles outside the boundary are considered lost. * @@ -48,6 +54,7 @@ namespace impactx * @param dx horizontal translation error in m * @param dy vertical translation error in m * @param rotation_degree rotation error in the transverse plane [degrees] + * @param name a user defined and not necessarily unique name of the element */ Aperture ( amrex::ParticleReal xmax, @@ -55,9 +62,11 @@ namespace impactx Shape shape, amrex::ParticleReal dx = 0, amrex::ParticleReal dy = 0, - amrex::ParticleReal rotation_degree = 0 + amrex::ParticleReal rotation_degree = 0, + std::optional name = std::nullopt ) - : Alignment(dx, dy, rotation_degree), + : Named(name), + Alignment(dx, dy, rotation_degree), m_shape(shape), m_xmax(xmax), m_ymax(ymax) { } @@ -102,13 +111,13 @@ namespace impactx switch (m_shape) { case Shape::rectangular : // default - if (pow(u,2)>1 || pow(v,2) > 1_prt) { + if (std::pow(u,2)>1 || std::pow(v,2) > 1_prt) { amrex::ParticleIDWrapper{idcpu}.make_invalid(); } break; case Shape::elliptical : - if (pow(u,2)+pow(v,2) > 1_prt) { + if (std::pow(u,2) + std::pow(v,2) > 1_prt) { amrex::ParticleIDWrapper{idcpu}.make_invalid(); } break; diff --git a/src/particles/elements/Aperture.cpp b/src/particles/elements/Aperture.cpp new file mode 100644 index 000000000..c647bca7c --- /dev/null +++ b/src/particles/elements/Aperture.cpp @@ -0,0 +1,28 @@ +/* Copyright 2022-2023 The Regents of the University of California, through Lawrence + * Berkeley National Laboratory (subject to receipt of any required + * approvals from the U.S. Dept. of Energy). All rights reserved. + * + * This file is part of ImpactX. + * + * Authors: Chad Mitchell, Axel Huebl + * License: BSD-3-Clause-LBNL + */ +#include "Aperture.H" + +#include +#include + + +std::string +impactx::Aperture::shape_name (Shape const & shape) +{ + switch (shape) + { + case Aperture::Shape::rectangular : // default + return "rectangular"; + case Aperture::Shape::elliptical : + return "elliptical"; + default: + throw std::runtime_error("Unknown shape"); + } +} diff --git a/src/particles/elements/Buncher.H b/src/particles/elements/Buncher.H index 1d6e3a4b7..bccbdab6a 100644 --- a/src/particles/elements/Buncher.H +++ b/src/particles/elements/Buncher.H @@ -14,6 +14,7 @@ #include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thin.H" +#include "mixin/named.H" #include "mixin/nofinalize.H" #include @@ -25,12 +26,13 @@ namespace impactx { struct Buncher - : public elements::BeamOptic, + : public elements::Named, + public elements::BeamOptic, public elements::Thin, public elements::Alignment, public elements::NoFinalize { - static constexpr auto name = "Buncher"; + static constexpr auto type = "Buncher"; using PType = ImpactXParticleContainer::ParticleType; /** A short RF cavity element at zero crossing for bunching @@ -40,15 +42,18 @@ namespace impactx * @param dx horizontal translation error in m * @param dy vertical translation error in m * @param rotation_degree rotation error in the transverse plane [degrees] + * @param name a user defined and not necessarily unique name of the element */ Buncher ( amrex::ParticleReal V, amrex::ParticleReal k, amrex::ParticleReal dx = 0, amrex::ParticleReal dy = 0, - amrex::ParticleReal rotation_degree = 0 + amrex::ParticleReal rotation_degree = 0, + std::optional name = std::nullopt ) - : Alignment(dx, dy, rotation_degree), + : Named(name), + Alignment(dx, dy, rotation_degree), m_V(V), m_k(k) { } @@ -86,7 +91,7 @@ namespace impactx // access reference particle values to find (beta*gamma)^2 amrex::ParticleReal const pt_ref = refpart.pt; - amrex::ParticleReal const betgam2 = pow(pt_ref, 2) - 1.0_prt; + amrex::ParticleReal const betgam2 = std::pow(pt_ref, 2) - 1.0_prt; // intialize output values of momenta amrex::ParticleReal pxout = px; diff --git a/src/particles/elements/CFbend.H b/src/particles/elements/CFbend.H index 5e100e186..bd42851ac 100644 --- a/src/particles/elements/CFbend.H +++ b/src/particles/elements/CFbend.H @@ -14,6 +14,7 @@ #include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thick.H" +#include "mixin/named.H" #include "mixin/nofinalize.H" #include @@ -26,12 +27,13 @@ namespace impactx { struct CFbend - : public elements::BeamOptic, + : public elements::Named, + public elements::BeamOptic, public elements::Thick, public elements::Alignment, public elements::NoFinalize { - static constexpr auto name = "CFbend"; + static constexpr auto type = "CFbend"; using PType = ImpactXParticleContainer::ParticleType; /** An combined-function bend, consisting of an ideal sector bend with @@ -47,6 +49,7 @@ namespace impactx * @param dy vertical translation error in m * @param rotation_degree rotation error in the transverse plane [degrees] * @param nslice number of slices used for the application of space charge + * @param name a user defined and not necessarily unique name of the element */ CFbend ( amrex::ParticleReal ds, @@ -55,9 +58,11 @@ namespace impactx amrex::ParticleReal dx = 0, amrex::ParticleReal dy = 0, amrex::ParticleReal rotation_degree = 0, - int nslice = 1 + int nslice = 1, + std::optional name = std::nullopt ) - : Thick(ds, nslice), + : Named(name), + Thick(ds, nslice), Alignment(dx, dy, rotation_degree), m_rc(rc), m_k(k) { @@ -109,18 +114,18 @@ namespace impactx // access reference particle values to find beta*gamma^2 amrex::ParticleReal const pt_ref = refpart.pt; - amrex::ParticleReal const betgam2 = pow(pt_ref, 2) - 1.0_prt; - amrex::ParticleReal const bet = sqrt(betgam2/(1.0_prt + betgam2)); + amrex::ParticleReal const betgam2 = std::pow(pt_ref, 2) - 1.0_prt; + amrex::ParticleReal const bet = std::sqrt(betgam2/(1.0_prt + betgam2)); // update horizontal and longitudinal phase space variables - amrex::ParticleReal const gx = m_k + pow(m_rc,-2); - amrex::ParticleReal const omegax = sqrt(std::abs(gx)); + amrex::ParticleReal const gx = m_k + std::pow(m_rc,-2); + amrex::ParticleReal const omegax = std::sqrt(std::abs(gx)); if(gx > 0.0) { // calculate expensive terms once auto const [sinx, cosx] = amrex::Math::sincos(omegax * slice_ds); amrex::ParticleReal const r56 = slice_ds/betgam2 - + (sinx - omegax*slice_ds)/(gx*omegax*pow(bet,2)*pow(m_rc,2)); + + (sinx - omegax*slice_ds)/(gx*omegax * std::pow(bet,2) * std::pow(m_rc,2)); // advance position and momentum (focusing) x = cosx*xout + sinx/omegax*px - (1.0_prt - cosx)/(gx*bet*m_rc)*pt; @@ -131,10 +136,10 @@ namespace impactx ptout = pt; } else { // calculate expensive terms once - amrex::ParticleReal const sinhx = sinh(omegax * slice_ds); - amrex::ParticleReal const coshx = cosh(omegax * slice_ds); + amrex::ParticleReal const sinhx = std::sinh(omegax * slice_ds); + amrex::ParticleReal const coshx = std::cosh(omegax * slice_ds); amrex::ParticleReal const r56 = slice_ds/betgam2 - + (sinhx - omegax*slice_ds)/(gx*omegax*pow(bet,2)*pow(m_rc,2)); + + (sinhx - omegax*slice_ds)/(gx*omegax * std::pow(bet,2) * std::pow(m_rc,2)); // advance position and momentum (defocusing) x = coshx*xout + sinhx/omegax*px - (1.0_prt - coshx)/(gx*bet*m_rc)*pt; @@ -147,7 +152,7 @@ namespace impactx // update vertical phase space variables amrex::ParticleReal const gy = -m_k; - amrex::ParticleReal const omegay = sqrt(std::abs(gy)); + amrex::ParticleReal const omegay = std::sqrt(std::abs(gy)); if(gy > 0.0) { // calculate expensive terms once @@ -159,8 +164,8 @@ namespace impactx } else { // calculate expensive terms once - amrex::ParticleReal const sinhy = sinh(omegay * slice_ds); - amrex::ParticleReal const coshy = cosh(omegay * slice_ds); + amrex::ParticleReal const sinhy = std::sinh(omegay * slice_ds); + amrex::ParticleReal const coshy = std::cosh(omegay * slice_ds); // advance position and momentum (defocusing) y = coshy*yout + sinhy/omegay*py; @@ -201,7 +206,7 @@ namespace impactx // assign intermediate parameter amrex::ParticleReal const theta = slice_ds/m_rc; - amrex::ParticleReal const B = sqrt(pow(pt,2)-1.0_prt)/m_rc; + amrex::ParticleReal const B = std::sqrt(std::pow(pt,2)-1.0_prt)/m_rc; // calculate expensive terms once auto const [sin_theta, cos_theta] = amrex::Math::sincos(theta); diff --git a/src/particles/elements/CMakeLists.txt b/src/particles/elements/CMakeLists.txt index c7acb5785..dceec99c3 100644 --- a/src/particles/elements/CMakeLists.txt +++ b/src/particles/elements/CMakeLists.txt @@ -1,5 +1,6 @@ target_sources(lib PRIVATE + Aperture.cpp Programmable.cpp ) diff --git a/src/particles/elements/ChrDrift.H b/src/particles/elements/ChrDrift.H index 337a3eca7..e042bbff2 100644 --- a/src/particles/elements/ChrDrift.H +++ b/src/particles/elements/ChrDrift.H @@ -14,6 +14,7 @@ #include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thick.H" +#include "mixin/named.H" #include "mixin/nofinalize.H" #include @@ -25,12 +26,13 @@ namespace impactx { struct ChrDrift - : public elements::BeamOptic, + : public elements::Named, + public elements::BeamOptic, public elements::Thick, public elements::Alignment, public elements::NoFinalize { - static constexpr auto name = "ChrDrift"; + static constexpr auto type = "ChrDrift"; using PType = ImpactXParticleContainer::ParticleType; /** A drift with chromatic effects included @@ -43,15 +45,18 @@ namespace impactx * @param dy vertical translation error in m * @param rotation_degree rotation error in the transverse plane [degrees] * @param nslice number of slices used for the application of space charge + * @param name a user defined and not necessarily unique name of the element */ ChrDrift ( amrex::ParticleReal ds, amrex::ParticleReal dx = 0, amrex::ParticleReal dy = 0, amrex::ParticleReal rotation_degree = 0, - int nslice = 1 + int nslice = 1, + std::optional name = std::nullopt ) - : Thick(ds, nslice), + : Named(name), + Thick(ds, nslice), Alignment(dx, dy, rotation_degree) { } @@ -106,7 +111,7 @@ namespace impactx // compute particle momentum deviation delta + 1 amrex::ParticleReal delta1; - delta1 = sqrt(1_prt - 2_prt*pt/bet + pow(pt,2)); + delta1 = std::sqrt(1_prt - 2_prt*pt/bet + std::pow(pt,2)); // advance transverse position and momentum (drift) x = xout + slice_ds * px / delta1; @@ -115,12 +120,12 @@ namespace impactx // pyout = py; // the corresponding symplectic update to t - amrex::ParticleReal term = 2_prt*pow(pt,2)+pow(px,2)+pow(py,2); - term = 2_prt - 4_prt*bet*pt + pow(bet,2)*term; - term = -2_prt + pow(gam,2)*term; + amrex::ParticleReal term = 2_prt * std::pow(pt,2) + std::pow(px,2) + std::pow(py,2); + term = 2_prt - 4_prt*bet*pt + std::pow(bet,2)*term; + term = -2_prt + std::pow(gam,2)*term; term = (-1_prt+bet*pt)*term; - term = term/(2_prt*pow(bet,3)*pow(gam,2)); - t = tout - slice_ds * (1_prt / bet + term / pow(delta1, 3)); + term = term/(2_prt * std::pow(bet,3) * std::pow(gam,2)); + t = tout - slice_ds * (1_prt / bet + term /std::pow(delta1, 3)); // ptout = pt; // assign updated momenta @@ -156,7 +161,7 @@ namespace impactx amrex::ParticleReal const slice_ds = m_ds / nslice(); // assign intermediate parameter - amrex::ParticleReal const step = slice_ds / sqrt(pow(pt,2)-1.0_prt); + amrex::ParticleReal const step = slice_ds /std::sqrt(std::pow(pt,2)-1.0_prt); // advance position and momentum (drift) refpart.x = x + step*px; diff --git a/src/particles/elements/ChrPlasmaLens.H b/src/particles/elements/ChrPlasmaLens.H index a32e71c34..4a4dfbb3b 100644 --- a/src/particles/elements/ChrPlasmaLens.H +++ b/src/particles/elements/ChrPlasmaLens.H @@ -14,6 +14,7 @@ #include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thick.H" +#include "mixin/named.H" #include "mixin/nofinalize.H" #include @@ -25,12 +26,13 @@ namespace impactx { struct ChrPlasmaLens - : public elements::BeamOptic, + : public elements::Named, + public elements::BeamOptic, public elements::Thick, public elements::Alignment, public elements::NoFinalize { - static constexpr auto name = "ChrPlasmaLens"; + static constexpr auto type = "ChrPlasmaLens"; using PType = ImpactXParticleContainer::ParticleType; /** An active cylindrically symmetric plasma lens with chromatic focusing @@ -49,6 +51,7 @@ namespace impactx * @param dy vertical translation error in m * @param rotation_degree rotation error in the transverse plane [degrees] * @param nslice number of slices used for the application of space charge + * @param name a user defined and not necessarily unique name of the element */ ChrPlasmaLens ( amrex::ParticleReal ds, @@ -57,9 +60,11 @@ namespace impactx amrex::ParticleReal dx = 0, amrex::ParticleReal dy = 0, amrex::ParticleReal rotation_degree = 0, - int nslice = 1 + int nslice = 1, + std::optional name = std::nullopt ) - : Thick(ds, nslice), + : Named(name), + Thick(ds, nslice), Alignment(dx, dy, rotation_degree), m_k(k), m_unit(unit) { @@ -110,12 +115,12 @@ namespace impactx // compute particle momentum deviation delta + 1 amrex::ParticleReal delta1; - delta1 = sqrt(1_prt - 2_prt*pt/bet + pow(pt,2)); + delta1 = std::sqrt(1_prt - 2_prt*pt/bet + std::pow(pt,2)); amrex::ParticleReal const delta = delta1 - 1_prt; // compute phase advance per unit length in s (in rad/m) // chromatic dependence on delta is included - amrex::ParticleReal const omega = sqrt(std::abs(g)/delta1); + amrex::ParticleReal const omega = std::sqrt(std::abs(g)/delta1); // initialize output values amrex::ParticleReal xout = x; @@ -148,13 +153,13 @@ namespace impactx amrex::ParticleReal const t0 = t - term*slice_ds/delta1; amrex::ParticleReal const w = omega*delta1; - amrex::ParticleReal const term1 = -(pow(p2,2)-pow(q2,2)*pow(w,2))*sin(2_prt*slice_ds*omega); - amrex::ParticleReal const term2 = -(pow(p1,2)-pow(q1,2)*pow(w,2))*sin(2_prt*slice_ds*omega); - amrex::ParticleReal const term3 = -2_prt*q2*p2*w*cos(2_prt*slice_ds*omega); - amrex::ParticleReal const term4 = -2_prt*q1*p1*w*cos(2_prt*slice_ds*omega); + amrex::ParticleReal const term1 = -(std::pow(p2,2)-pow(q2,2) * std::pow(w,2)) * std::sin(2_prt*slice_ds*omega); + amrex::ParticleReal const term2 = -(std::pow(p1,2)-pow(q1,2) * std::pow(w,2)) * std::sin(2_prt*slice_ds*omega); + amrex::ParticleReal const term3 = -2_prt*q2*p2*w * std::cos(2_prt*slice_ds*omega); + amrex::ParticleReal const term4 = -2_prt*q1*p1*w * std::cos(2_prt*slice_ds*omega); amrex::ParticleReal const term5 = 2_prt*omega*(q1*p1*delta1 + q2*p2*delta1 - -(pow(p1,2)+pow(p2,2))*slice_ds - (pow(q1,2)+pow(q2,2))*pow(w,2)*slice_ds); - t = t0 + (-1_prt+bet*pt)/(8_prt*bet*pow(delta1,3)*omega) + -(std::pow(p1,2) + std::pow(p2,2))*slice_ds - (std::pow(q1,2) + std::pow(q2,2)) * std::pow(w,2)*slice_ds); + t = t0 + (-1_prt+bet*pt)/(8_prt*bet * std::pow(delta1,3)*omega) *(term1+term2+term3+term4+term5); // ptout = pt; @@ -194,7 +199,7 @@ namespace impactx amrex::ParticleReal const slice_ds = m_ds / nslice(); // assign intermediate parameter - amrex::ParticleReal const step = slice_ds / sqrt(pow(pt,2)-1.0_prt); + amrex::ParticleReal const step = slice_ds /std::sqrt(std::pow(pt,2)-1.0_prt); // advance position and momentum (straight element) refpart.x = x + step*px; diff --git a/src/particles/elements/ChrQuad.H b/src/particles/elements/ChrQuad.H index 02463f46e..7672fc3a9 100644 --- a/src/particles/elements/ChrQuad.H +++ b/src/particles/elements/ChrQuad.H @@ -14,6 +14,7 @@ #include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thick.H" +#include "mixin/named.H" #include "mixin/nofinalize.H" #include @@ -26,12 +27,13 @@ namespace impactx { struct ChrQuad - : public elements::BeamOptic, + : public elements::Named, + public elements::BeamOptic, public elements::Thick, public elements::Alignment, public elements::NoFinalize { - static constexpr auto name = "ChrQuad"; + static constexpr auto type = "ChrQuad"; using PType = ImpactXParticleContainer::ParticleType; /** A Quadrupole magnet with chromatic focusing @@ -52,6 +54,7 @@ namespace impactx * @param dy vertical translation error in m * @param rotation_degree rotation error in the transverse plane [degrees] * @param nslice number of slices used for the application of space charge + * @param name a user defined and not necessarily unique name of the element */ ChrQuad ( amrex::ParticleReal ds, @@ -60,9 +63,11 @@ namespace impactx amrex::ParticleReal dx = 0, amrex::ParticleReal dy = 0, amrex::ParticleReal rotation_degree = 0, - int nslice = 1 + int nslice = 1, + std::optional name = std::nullopt ) - : Thick(ds, nslice), + : Named(name), + Thick(ds, nslice), Alignment(dx, dy, rotation_degree), m_k(k), m_unit(unit) { @@ -118,12 +123,12 @@ namespace impactx // compute particle momentum deviation delta + 1 amrex::ParticleReal delta1; - delta1 = sqrt(1_prt - 2_prt*pt/bet + pow(pt,2)); + delta1 = std::sqrt(1_prt - 2_prt*pt/bet + std::pow(pt,2)); amrex::ParticleReal const delta = delta1 - 1_prt; // compute phase advance per unit length in s (in rad/m) // chromatic dependence on delta is included - amrex::ParticleReal const omega = sqrt(std::abs(g)/delta1); + amrex::ParticleReal const omega = std::sqrt(std::abs(g)/delta1); // intialize output values of momenta amrex::ParticleReal pxout = px; @@ -138,23 +143,23 @@ namespace impactx if(g > 0.0) { // advance transverse position and momentum (focusing quad) - x = cos(omega*slice_ds) * xout + - sin(omega*slice_ds)/(omega*delta1)*px; - pxout = -omega * delta1 * sin(omega*slice_ds) * xout + cos(omega * slice_ds) * px; + x = std::cos(omega*slice_ds) * xout + + std::sin(omega*slice_ds)/(omega*delta1)*px; + pxout = -omega * delta1 *std::sin(omega*slice_ds) * xout + std::cos(omega * slice_ds) * px; - y = cosh(omega*slice_ds) * yout + - sinh(omega*slice_ds)/(omega*delta1)*py; - pyout = omega * delta1 * sinh(omega*slice_ds) * yout + cosh(omega * slice_ds) * py; + y = std::cosh(omega*slice_ds) * yout + + std::sinh(omega*slice_ds)/(omega*delta1)*py; + pyout = omega * delta1 * std::sinh(omega*slice_ds) * yout + std::cosh(omega * slice_ds) * py; } else { // advance transverse position and momentum (defocusing quad) - x = cosh(omega*slice_ds) * xout + - sinh(omega*slice_ds)/(omega*delta1)*px; - pxout = omega * delta1 * sinh(omega*slice_ds) * xout + cosh(omega * slice_ds) * px; + x = std::cosh(omega*slice_ds) * xout + + std::sinh(omega*slice_ds)/(omega*delta1)*px; + pxout = omega * delta1 * std::sinh(omega*slice_ds) * xout + std::cosh(omega * slice_ds) * px; - y = cos(omega*slice_ds) * yout + - sin(omega*slice_ds)/(omega*delta1)*py; - pyout = -omega * delta1 * sin(omega*slice_ds) * yout + cos(omega * slice_ds) * py; + y = std::cos(omega*slice_ds) * yout + + std::sin(omega*slice_ds)/(omega*delta1)*py; + pyout = -omega * delta1 *std::sin(omega*slice_ds) * yout + std::cos(omega * slice_ds) * py; q1 = yout; q2 = xout; @@ -169,13 +174,13 @@ namespace impactx amrex::ParticleReal const t0 = tout - term * slice_ds / delta1; amrex::ParticleReal const w = omega*delta1; - amrex::ParticleReal const term1 = -(pow(p2,2)+pow(q2,2)*pow(w,2))*sinh(2_prt*slice_ds*omega); - amrex::ParticleReal const term2 = -(pow(p1,2)-pow(q1,2)*pow(w,2))*sin(2_prt*slice_ds*omega); - amrex::ParticleReal const term3 = -2_prt*q2*p2*w*cosh(2_prt*slice_ds*omega); - amrex::ParticleReal const term4 = -2_prt*q1*p1*w*cos(2_prt*slice_ds*omega); + amrex::ParticleReal const term1 = -(std::pow(p2,2) + std::pow(q2,2) * std::pow(w,2))*std::sinh(2_prt*slice_ds*omega); + amrex::ParticleReal const term2 = -(std::pow(p1,2)-pow(q1,2) * std::pow(w,2)) * std::sin(2_prt*slice_ds*omega); + amrex::ParticleReal const term3 = -2_prt*q2*p2*w*std::cosh(2_prt*slice_ds*omega); + amrex::ParticleReal const term4 = -2_prt*q1*p1*w * std::cos(2_prt*slice_ds*omega); amrex::ParticleReal const term5 = 2_prt*omega*(q1*p1*delta1 + q2*p2*delta1 - -(pow(p1,2)+pow(p2,2))*slice_ds - (pow(q1,2)-pow(q2,2))*pow(w,2)*slice_ds); - t = t0 + (-1_prt+bet*pt)/(8_prt*bet*pow(delta1,3)*omega) + -(std::pow(p1,2) + std::pow(p2,2))*slice_ds - (std::pow(q1,2)-pow(q2,2)) * std::pow(w,2)*slice_ds); + t = t0 + (-1_prt+bet*pt)/(8_prt*bet * std::pow(delta1,3)*omega) *(term1+term2+term3+term4+term5); // ptout = pt; @@ -213,7 +218,7 @@ namespace impactx amrex::ParticleReal const slice_ds = m_ds / nslice(); // assign intermediate parameter - amrex::ParticleReal const step = slice_ds / sqrt(pow(pt,2)-1.0_prt); + amrex::ParticleReal const step = slice_ds /std::sqrt(std::pow(pt,2)-1.0_prt); // advance position and momentum (straight element) refpart.x = x + step*px; diff --git a/src/particles/elements/ChrUniformAcc.H b/src/particles/elements/ChrUniformAcc.H index dfbdd794f..9cb1087ce 100644 --- a/src/particles/elements/ChrUniformAcc.H +++ b/src/particles/elements/ChrUniformAcc.H @@ -14,6 +14,7 @@ #include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thick.H" +#include "mixin/named.H" #include "mixin/nofinalize.H" #include @@ -25,12 +26,13 @@ namespace impactx { struct ChrAcc - : public elements::BeamOptic, + : public elements::Named, + public elements::BeamOptic, public elements::Thick, public elements::Alignment, public elements::NoFinalize { - static constexpr auto name = "ChrAcc"; + static constexpr auto type = "ChrAcc"; using PType = ImpactXParticleContainer::ParticleType; /** Acceleration in a uniform field Ez, with a uniform solenoidal field Bz. @@ -47,6 +49,7 @@ namespace impactx * @param dy vertical translation error in m * @param rotation_degree rotation error in the transverse plane [degrees] * @param nslice number of slices used for the application of space charge + * @param name a user defined and not necessarily unique name of the element */ ChrAcc ( amrex::ParticleReal ds, @@ -55,9 +58,11 @@ namespace impactx amrex::ParticleReal dx = 0, amrex::ParticleReal dy = 0, amrex::ParticleReal rotation_degree = 0, - int nslice = 1 + int nslice = 1, + std::optional name = std::nullopt ) - : Thick(ds, nslice), + : Named(name), + Thick(ds, nslice), Alignment(dx, dy, rotation_degree), m_ez(ez), m_bz(bz) { @@ -100,8 +105,8 @@ namespace impactx // access reference particle values (final, initial): amrex::ParticleReal const ptf_ref = refpart.pt; amrex::ParticleReal const pti_ref = ptf_ref + m_ez*slice_ds; - amrex::ParticleReal const bgf = sqrt(pow(ptf_ref, 2) - 1.0_prt); - amrex::ParticleReal const bgi = sqrt(pow(pti_ref, 2) - 1.0_prt); + amrex::ParticleReal const bgf = std::sqrt(std::pow(ptf_ref, 2) - 1.0_prt); + amrex::ParticleReal const bgi = std::sqrt(std::pow(pti_ref, 2) - 1.0_prt); // initial conversion from static to dynamic units: px = px*bgi; @@ -111,17 +116,17 @@ namespace impactx // compute intermediate quantities related to acceleration amrex::ParticleReal const pti_tot = pti_ref + pt; amrex::ParticleReal const ptf_tot = ptf_ref + pt; - amrex::ParticleReal const pzi_tot = sqrt(pow(pti_tot,2)-1_prt); - amrex::ParticleReal const pzf_tot = sqrt(pow(ptf_tot,2)-1_prt); - amrex::ParticleReal const pzi_ref = sqrt(pow(pti_ref,2)-1_prt); - amrex::ParticleReal const pzf_ref = sqrt(pow(ptf_ref,2)-1_prt); + amrex::ParticleReal const pzi_tot = std::sqrt(std::pow(pti_tot,2)-1_prt); + amrex::ParticleReal const pzf_tot = std::sqrt(std::pow(ptf_tot,2)-1_prt); + amrex::ParticleReal const pzi_ref = std::sqrt(std::pow(pti_ref,2)-1_prt); + amrex::ParticleReal const pzf_ref = std::sqrt(std::pow(ptf_ref,2)-1_prt); amrex::ParticleReal const numer = -ptf_tot + pzf_tot; amrex::ParticleReal const denom = -pti_tot + pzi_tot; // compute focusing constant (1/m) and rotation angle (in rad) amrex::ParticleReal const alpha = m_bz/2.0_prt; - amrex::ParticleReal const theta = alpha/m_ez*log(numer/denom); + amrex::ParticleReal const theta = alpha/m_ez*std::log(numer/denom); // intialize output values amrex::ParticleReal xout = x; @@ -132,15 +137,15 @@ namespace impactx amrex::ParticleReal ptout = pt; // advance positions and momenta using map for focusing - xout = cos(theta)*x + sin(theta)/alpha*px; - pxout = -alpha*sin(theta)*x + cos(theta)*px; + xout = std::cos(theta)*x + std::sin(theta)/alpha*px; + pxout = -alpha * std::sin(theta)*x + std::cos(theta)*px; - yout = cos(theta)*y + sin(theta)/alpha*py; - pyout = -alpha*sin(theta)*y + cos(theta)*py; + yout = std::cos(theta)*y + std::sin(theta)/alpha*py; + pyout = -alpha * std::sin(theta)*y + std::cos(theta)*py; // the correct symplectic update for t tout = t + (pzf_tot - pzf_ref - pzi_tot + pzi_ref)/m_ez; - tout = tout + (1_prt/pzi_tot - 1_prt/pzf_tot)*(pow(py-alpha*x,2)+pow(px+alpha*y,2))/(2_prt*m_ez); + tout = tout + (1_prt/pzi_tot - 1_prt/pzf_tot)*(std::pow(py-alpha*x,2) + std::pow(px+alpha*y,2))/(2_prt*m_ez); ptout = pt; // assign intermediate momenta @@ -149,11 +154,11 @@ namespace impactx pt = ptout; // advance positions and momenta using map for rotation - x = cos(theta)*xout + sin(theta)*yout; - pxout = cos(theta)*px + sin(theta)*py; + x = std::cos(theta)*xout + std::sin(theta)*yout; + pxout = std::cos(theta)*px + std::sin(theta)*py; - y = -sin(theta)*xout + cos(theta)*yout; - pyout = -sin(theta)*px + cos(theta)*py; + y = -std::sin(theta)*xout + std::cos(theta)*yout; + pyout = -std::sin(theta)*px + std::cos(theta)*py; t = tout; ptout = pt; @@ -196,14 +201,14 @@ namespace impactx amrex::ParticleReal const slice_ds = m_ds / nslice(); // compute intial value of beta*gamma - amrex::ParticleReal const bgi = sqrt(pow(pt, 2) - 1.0_prt); + amrex::ParticleReal const bgi = std::sqrt(std::pow(pt, 2) - 1.0_prt); // advance pt (uniform acceleration) refpart.pt = pt - m_ez*slice_ds; // compute final value of beta*gamma amrex::ParticleReal const ptf = refpart.pt; - amrex::ParticleReal const bgf = sqrt(pow(ptf, 2) - 1.0_prt); + amrex::ParticleReal const bgf = std::sqrt(std::pow(ptf, 2) - 1.0_prt); // update t refpart.t = t + (bgf - bgi)/m_ez; diff --git a/src/particles/elements/ConstF.H b/src/particles/elements/ConstF.H index 41d390a38..a1b29c676 100644 --- a/src/particles/elements/ConstF.H +++ b/src/particles/elements/ConstF.H @@ -14,6 +14,7 @@ #include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thick.H" +#include "mixin/named.H" #include "mixin/nofinalize.H" #include @@ -25,12 +26,13 @@ namespace impactx { struct ConstF - : public elements::BeamOptic, + : public elements::Named, + public elements::BeamOptic, public elements::Thick, public elements::Alignment, public elements::NoFinalize { - static constexpr auto name = "ConstF"; + static constexpr auto type = "ConstF"; using PType = ImpactXParticleContainer::ParticleType; /** A linear Constant Focusing element @@ -43,6 +45,7 @@ namespace impactx * @param dy vertical translation error in m * @param rotation_degree rotation error in the transverse plane [degrees] * @param nslice number of slices used for the application of space charge + * @param name a user defined and not necessarily unique name of the element */ ConstF ( amrex::ParticleReal ds, @@ -52,9 +55,11 @@ namespace impactx amrex::ParticleReal dx = 0, amrex::ParticleReal dy = 0, amrex::ParticleReal rotation_degree = 0, - int nslice = 1 + int nslice = 1, + std::optional name = std::nullopt ) - : Thick(ds, nslice), + : Named(name), + Thick(ds, nslice), Alignment(dx, dy, rotation_degree), m_kx(kx), m_ky(ky), m_kt(kt) { @@ -92,7 +97,7 @@ namespace impactx // access reference particle values to find beta*gamma^2 amrex::ParticleReal const pt_ref = refpart.pt; - amrex::ParticleReal const betgam2 = pow(pt_ref, 2) - 1.0_prt; + amrex::ParticleReal const betgam2 = std::pow(pt_ref, 2) - 1.0_prt; // intialize output values amrex::ParticleReal xout = x; @@ -106,14 +111,14 @@ namespace impactx amrex::ParticleReal const slice_ds = m_ds / nslice(); // advance position and momentum - xout = cos(m_kx*slice_ds)*x + sin(m_kx*slice_ds)/m_kx*px; - pxout = -m_kx*sin(m_kx*slice_ds)*x + cos(m_kx*slice_ds)*px; + xout = std::cos(m_kx*slice_ds)*x + std::sin(m_kx*slice_ds)/m_kx*px; + pxout = -m_kx * std::sin(m_kx*slice_ds)*x + std::cos(m_kx*slice_ds)*px; - yout = cos(m_ky*slice_ds)*y + sin(m_ky*slice_ds)/m_ky*py; - pyout = -m_ky*sin(m_ky*slice_ds)*y + cos(m_ky*slice_ds)*py; + yout = std::cos(m_ky*slice_ds)*y + std::sin(m_ky*slice_ds)/m_ky*py; + pyout = -m_ky * std::sin(m_ky*slice_ds)*y + std::cos(m_ky*slice_ds)*py; - tout = cos(m_kt*slice_ds)*t + sin(m_kt*slice_ds)/(betgam2*m_kt)*pt; - ptout = -(m_kt*betgam2)*sin(m_kt*slice_ds)*t + cos(m_kt*slice_ds)*pt; + tout = std::cos(m_kt*slice_ds)*t + std::sin(m_kt*slice_ds)/(betgam2*m_kt)*pt; + ptout = -(m_kt*betgam2) * std::sin(m_kt*slice_ds)*t + std::cos(m_kt*slice_ds)*pt; // assign updated values x = xout; @@ -151,7 +156,7 @@ namespace impactx amrex::ParticleReal const slice_ds = m_ds / nslice(); // assign intermediate parameter - amrex::ParticleReal const step = slice_ds / sqrt(pow(pt, 2)-1.0_prt); + amrex::ParticleReal const step = slice_ds /std::sqrt(std::pow(pt, 2)-1.0_prt); // advance position and momentum (straight element) refpart.x = x + step*px; diff --git a/src/particles/elements/DipEdge.H b/src/particles/elements/DipEdge.H index a5930f99a..e05ab0dbf 100644 --- a/src/particles/elements/DipEdge.H +++ b/src/particles/elements/DipEdge.H @@ -14,6 +14,7 @@ #include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thin.H" +#include "mixin/named.H" #include "mixin/nofinalize.H" #include @@ -25,12 +26,13 @@ namespace impactx { struct DipEdge - : public elements::BeamOptic, + : public elements::Named, + public elements::BeamOptic, public elements::Thin, public elements::Alignment, public elements::NoFinalize { - static constexpr auto name = "DipEdge"; + static constexpr auto type = "DipEdge"; using PType = ImpactXParticleContainer::ParticleType; /** Edge focusing associated with bend entry or exit @@ -49,6 +51,7 @@ namespace impactx * @param dx horizontal translation error in m * @param dy vertical translation error in m * @param rotation_degree rotation error in the transverse plane [degrees] + * @param name a user defined and not necessarily unique name of the element */ DipEdge ( amrex::ParticleReal psi, @@ -57,9 +60,11 @@ namespace impactx amrex::ParticleReal K2, amrex::ParticleReal dx = 0, amrex::ParticleReal dy = 0, - amrex::ParticleReal rotation_degree = 0 + amrex::ParticleReal rotation_degree = 0, + std::optional name = std::nullopt ) - : Alignment(dx, dy, rotation_degree), + : Named(name), + Alignment(dx, dy, rotation_degree), m_psi(psi), m_rc(rc), m_g(g), m_K2(K2) { } @@ -96,13 +101,13 @@ namespace impactx shift_in(x, y, px, py); // edge focusing matrix elements (zero gap) - amrex::ParticleReal const R21 = tan(m_psi)/m_rc; + amrex::ParticleReal const R21 = std::tan(m_psi)/m_rc; amrex::ParticleReal R43 = -R21; amrex::ParticleReal vf = 0; // first-order effect of nonzero gap - vf = (1.0_prt + pow(sin(m_psi),2))/(pow(cos(m_psi),3)); - vf *= m_g * m_K2/(pow(m_rc,2)); + vf = (1.0_prt + std::pow(sin(m_psi),2))/(std::pow(cos(m_psi),3)); + vf *= m_g * m_K2/(std::pow(m_rc,2)); R43 += vf; // apply edge focusing diff --git a/src/particles/elements/Drift.H b/src/particles/elements/Drift.H index d4ef04acb..fcb4ff6f8 100644 --- a/src/particles/elements/Drift.H +++ b/src/particles/elements/Drift.H @@ -14,6 +14,7 @@ #include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thick.H" +#include "mixin/named.H" #include "mixin/nofinalize.H" #include @@ -25,12 +26,13 @@ namespace impactx { struct Drift - : public elements::BeamOptic, + : public elements::Named, + public elements::BeamOptic, public elements::Thick, public elements::Alignment, public elements::NoFinalize { - static constexpr auto name = "Drift"; + static constexpr auto type = "Drift"; using PType = ImpactXParticleContainer::ParticleType; /** A drift @@ -40,15 +42,18 @@ namespace impactx * @param dy vertical translation error in m * @param rotation_degree rotation error in the transverse plane [degrees] * @param nslice number of slices used for the application of space charge + * @param name a user defined and not necessarily unique name of the element */ Drift ( amrex::ParticleReal ds, amrex::ParticleReal dx = 0, amrex::ParticleReal dy = 0, amrex::ParticleReal rotation_degree = 0, - int nslice = 1 + int nslice = 1, + std::optional name = std::nullopt ) - : Thick(ds, nslice), + : Named(name), + Thick(ds, nslice), Alignment(dx, dy, rotation_degree) { } @@ -97,7 +102,7 @@ namespace impactx // access reference particle values to find beta*gamma^2 amrex::ParticleReal const pt_ref = refpart.pt; - amrex::ParticleReal const betgam2 = pow(pt_ref, 2) - 1.0_prt; + amrex::ParticleReal const betgam2 = std::pow(pt_ref, 2) - 1.0_prt; // advance position and momentum (drift) xout = x + slice_ds * px; @@ -143,7 +148,7 @@ namespace impactx amrex::ParticleReal const slice_ds = m_ds / nslice(); // assign intermediate parameter - amrex::ParticleReal const step = slice_ds / sqrt(pow(pt,2)-1.0_prt); + amrex::ParticleReal const step = slice_ds /std::sqrt(std::pow(pt,2)-1.0_prt); // advance position and momentum (drift) refpart.x = x + step*px; diff --git a/src/particles/elements/Empty.H b/src/particles/elements/Empty.H index cd3350d23..9ce3eece8 100644 --- a/src/particles/elements/Empty.H +++ b/src/particles/elements/Empty.H @@ -12,6 +12,7 @@ #include "particles/ImpactXParticleContainer.H" #include "mixin/thin.H" +#include "mixin/named.H" #include "mixin/nofinalize.H" #include @@ -24,7 +25,7 @@ namespace impactx : public elements::Thin, public elements::NoFinalize { - static constexpr auto name = "None"; + static constexpr auto type = "None"; using PType = ImpactXParticleContainer::ParticleType; /** This element does nothing. @@ -35,8 +36,9 @@ namespace impactx /** Push all particles - nothing to do here */ void operator() ( - ImpactXParticleContainer & /* pc */, - int /* step */ + ImpactXParticleContainer & /* pc */, + int /* step */, + int /* period */ ) { // nothing to do } @@ -62,14 +64,14 @@ namespace impactx */ AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void operator() ( - [[maybe_unused]] amrex::ParticleReal & AMREX_RESTRICT x, - [[maybe_unused]] amrex::ParticleReal & AMREX_RESTRICT y, - [[maybe_unused]] amrex::ParticleReal & AMREX_RESTRICT t, - [[maybe_unused]] amrex::ParticleReal & AMREX_RESTRICT px, - [[maybe_unused]] amrex::ParticleReal & AMREX_RESTRICT py, - [[maybe_unused]] amrex::ParticleReal & AMREX_RESTRICT pt, - [[maybe_unused]] uint64_t & AMREX_RESTRICT idcpu, - [[maybe_unused]] RefPart const & refpart + [[maybe_unused]] amrex::ParticleReal & AMREX_RESTRICT x, + [[maybe_unused]] amrex::ParticleReal & AMREX_RESTRICT y, + [[maybe_unused]] amrex::ParticleReal & AMREX_RESTRICT t, + [[maybe_unused]] amrex::ParticleReal & AMREX_RESTRICT px, + [[maybe_unused]] amrex::ParticleReal & AMREX_RESTRICT py, + [[maybe_unused]] amrex::ParticleReal & AMREX_RESTRICT pt, + [[maybe_unused]] uint64_t & AMREX_RESTRICT idcpu, + [[maybe_unused]] RefPart const & refpart ) const { // nothing to do diff --git a/src/particles/elements/ExactDrift.H b/src/particles/elements/ExactDrift.H index 2e732130f..2d976c300 100644 --- a/src/particles/elements/ExactDrift.H +++ b/src/particles/elements/ExactDrift.H @@ -14,6 +14,7 @@ #include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thick.H" +#include "mixin/named.H" #include "mixin/nofinalize.H" #include @@ -25,12 +26,13 @@ namespace impactx { struct ExactDrift - : public elements::BeamOptic, + : public elements::Named, + public elements::BeamOptic, public elements::Thick, public elements::Alignment, public elements::NoFinalize { - static constexpr auto name = "ExactDrift"; + static constexpr auto type = "ExactDrift"; using PType = ImpactXParticleContainer::ParticleType; /** A drift using the exact nonlinear transfer map @@ -40,15 +42,18 @@ namespace impactx * @param dy vertical translation error in m * @param rotation_degree rotation error in the transverse plane [degrees] * @param nslice number of slices used for the application of space charge + * @param name a user defined and not necessarily unique name of the element */ ExactDrift ( amrex::ParticleReal ds, amrex::ParticleReal dx = 0, amrex::ParticleReal dy = 0, amrex::ParticleReal rotation_degree = 0, - int nslice = 1 + int nslice = 1, + std::optional name = std::nullopt ) - : Thick(ds, nslice), + : Named(name), + Thick(ds, nslice), Alignment(dx, dy, rotation_degree) { } @@ -103,8 +108,8 @@ namespace impactx amrex::ParticleReal const betgam = refpart.beta_gamma(); // compute the radical in the denominator (= pz): - amrex::ParticleReal const pzden = sqrt(pow(pt-1_prt/bet,2) - - 1_prt/pow(betgam,2) - pow(px,2) - pow(py,2)); + amrex::ParticleReal const pzden = std::sqrt(std::pow(pt-1_prt/bet,2) - + 1_prt / std::pow(betgam,2) - std::pow(px,2) - std::pow(py,2)); // advance position and momentum (exact drift) x = xout + slice_ds * px / pzden; @@ -147,7 +152,7 @@ namespace impactx amrex::ParticleReal const slice_ds = m_ds / nslice(); // assign intermediate parameter - amrex::ParticleReal const step = slice_ds / sqrt(pow(pt,2)-1.0_prt); + amrex::ParticleReal const step = slice_ds /std::sqrt(std::pow(pt,2)-1.0_prt); // advance position and momentum (drift) refpart.x = x + step*px; diff --git a/src/particles/elements/ExactSbend.H b/src/particles/elements/ExactSbend.H index 5b40bd30f..f34cac473 100644 --- a/src/particles/elements/ExactSbend.H +++ b/src/particles/elements/ExactSbend.H @@ -14,6 +14,7 @@ #include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thick.H" +#include "mixin/named.H" #include "mixin/nofinalize.H" #include @@ -27,12 +28,13 @@ namespace impactx { struct ExactSbend - : public elements::BeamOptic, + : public elements::Named, + public elements::BeamOptic, public elements::Thick, public elements::Alignment, public elements::NoFinalize { - static constexpr auto name = "ExactSbend"; + static constexpr auto type = "ExactSbend"; static constexpr amrex::ParticleReal degree2rad = ablastr::constant::math::pi / 180.0; using PType = ImpactXParticleContainer::ParticleType; @@ -55,6 +57,7 @@ namespace impactx * @param dy vertical translation error in m * @param rotation_degree rotation error in the transverse plane [degrees] * @param nslice number of slices used for the application of space charge + * @param name a user defined and not necessarily unique name of the element */ ExactSbend ( amrex::ParticleReal ds, @@ -63,9 +66,11 @@ namespace impactx amrex::ParticleReal dx = 0, amrex::ParticleReal dy = 0, amrex::ParticleReal rotation_degree = 0, - int nslice = 1 + int nslice = 1, + std::optional name = std::nullopt ) - : Thick(ds, nslice), + : Named(name), + Thick(ds, nslice), Alignment(dx, dy, rotation_degree), m_phi(phi * degree2rad), m_B(B) { @@ -131,8 +136,8 @@ namespace impactx amrex::ParticleReal ptout = pt; // assign intermediate quantities - amrex::ParticleReal const pperp = sqrt(pow(pt,2)-2.0_prt/bet*pt-pow(py,2)+1.0_prt); - amrex::ParticleReal const pzi = sqrt(pow(pperp,2)-pow(px,2)); + amrex::ParticleReal const pperp = std::sqrt(std::pow(pt,2)-2.0_prt/bet*pt-pow(py,2)+1.0_prt); + amrex::ParticleReal const pzi = std::sqrt(std::pow(pperp,2) - std::pow(px,2)); amrex::ParticleReal const rho = rc + xout; auto const [sin_phi, cos_phi] = amrex::Math::sincos(slice_phi); @@ -142,8 +147,8 @@ namespace impactx ptout = pt; // angle of momentum rotation - amrex::ParticleReal const pzf = sqrt(pow(pperp,2)-pow(pxout,2)); - amrex::ParticleReal const theta = slice_phi + asin(px/pperp) - asin(pxout/pperp); + amrex::ParticleReal const pzf = std::sqrt(std::pow(pperp,2)-pow(pxout,2)); + amrex::ParticleReal const theta = slice_phi + std::asin(px/pperp) - std::asin(pxout/pperp); // update position coordinates x = -rc + rho*cos_phi + rc*(pzf + px*sin_phi - pzi*cos_phi); diff --git a/src/particles/elements/Kicker.H b/src/particles/elements/Kicker.H index 5eca2caef..3150900ab 100644 --- a/src/particles/elements/Kicker.H +++ b/src/particles/elements/Kicker.H @@ -14,6 +14,7 @@ #include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thin.H" +#include "mixin/named.H" #include "mixin/nofinalize.H" #include @@ -25,12 +26,13 @@ namespace impactx { struct Kicker - : public elements::BeamOptic, + : public elements::Named, + public elements::BeamOptic, public elements::Thin, public elements::Alignment, public elements::NoFinalize { - static constexpr auto name = "Kicker"; + static constexpr auto type = "Kicker"; using PType = ImpactXParticleContainer::ParticleType; enum UnitSystem @@ -48,6 +50,7 @@ namespace impactx * @param dx horizontal translation error in m * @param dy vertical translation error in m * @param rotation_degree rotation error in the transverse plane [degrees] + * @param name a user defined and not necessarily unique name of the element */ Kicker ( amrex::ParticleReal xkick, @@ -55,9 +58,11 @@ namespace impactx UnitSystem unit, amrex::ParticleReal dx = 0, amrex::ParticleReal dy = 0, - amrex::ParticleReal rotation_degree = 0 + amrex::ParticleReal rotation_degree = 0, + std::optional name = std::nullopt ) - : Alignment(dx, dy, rotation_degree), + : Named(name), + Alignment(dx, dy, rotation_degree), m_xkick(xkick), m_ykick(ykick), m_unit(unit) { } diff --git a/src/particles/elements/Marker.H b/src/particles/elements/Marker.H new file mode 100644 index 000000000..3291aff79 --- /dev/null +++ b/src/particles/elements/Marker.H @@ -0,0 +1,90 @@ +/* Copyright 2022-2023 The Regents of the University of California, through Lawrence + * Berkeley National Laboratory (subject to receipt of any required + * approvals from the U.S. Dept. of Energy). All rights reserved. + * + * This file is part of ImpactX. + * + * Authors: Axel Huebl + * License: BSD-3-Clause-LBNL + */ +#ifndef IMPACTX_ELEMENT_MARKER_H +#define IMPACTX_ELEMENT_MARKER_H + +#include "particles/ImpactXParticleContainer.H" +#include "mixin/thin.H" +#include "mixin/named.H" +#include "mixin/nofinalize.H" + +#include +#include + + +namespace impactx +{ + struct Marker + : public elements::Named, + public elements::Thin, + public elements::NoFinalize + { + static constexpr auto type = "Marker"; + using PType = ImpactXParticleContainer::ParticleType; + + /** This named element does nothing. + * + * @param name a user defined and not necessarily unique name of the element + */ + Marker (std::string name) + : Named(name) + { + } + + /** Push all particles - nothing to do here */ + void operator() ( + ImpactXParticleContainer & /* pc */, + int /* step */, + int /* period */ + ) { + // nothing to do + } + + /** Push all particles - nothing to do here */ + void operator() ( + ImpactXParticleContainer::iterator & /* pti */, + RefPart & AMREX_RESTRICT /* ref_part */ + ) { + // nothing to do + } + + /** Does nothing to a particle. + * + * @param x particle position in x + * @param y particle position in y + * @param t particle position in t + * @param px particle momentum in x + * @param py particle momentum in y + * @param pt particle momentum in t + * @param idcpu particle global index (unused) + * @param refpart reference particle + */ + AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE + void operator() ( + [[maybe_unused]] amrex::ParticleReal & AMREX_RESTRICT x, + [[maybe_unused]] amrex::ParticleReal & AMREX_RESTRICT y, + [[maybe_unused]] amrex::ParticleReal & AMREX_RESTRICT t, + [[maybe_unused]] amrex::ParticleReal & AMREX_RESTRICT px, + [[maybe_unused]] amrex::ParticleReal & AMREX_RESTRICT py, + [[maybe_unused]] amrex::ParticleReal & AMREX_RESTRICT pt, + [[maybe_unused]] uint64_t & AMREX_RESTRICT idcpu, + [[maybe_unused]] RefPart const & refpart + ) const + { + // nothing to do + } + + /** This pushes the reference particle. */ + using Thin::operator(); + }; + +} // namespace impactx + +#endif // IMPACTX_ELEMENT_MARKER_H diff --git a/src/particles/elements/Multipole.H b/src/particles/elements/Multipole.H index 6185818b5..6f4814a2c 100644 --- a/src/particles/elements/Multipole.H +++ b/src/particles/elements/Multipole.H @@ -14,6 +14,7 @@ #include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thin.H" +#include "mixin/named.H" #include "mixin/nofinalize.H" #include @@ -25,12 +26,13 @@ namespace impactx { struct Multipole - : public elements::BeamOptic, + : public elements::Named, + public elements::BeamOptic, public elements::Thin, public elements::Alignment, public elements::NoFinalize { - static constexpr auto name = "Multipole"; + static constexpr auto type = "Multipole"; using PType = ImpactXParticleContainer::ParticleType; /** A general thin multipole element @@ -41,6 +43,7 @@ namespace impactx * @param dx horizontal translation error in m * @param dy vertical translation error in m * @param rotation_degree rotation error in the transverse plane [degrees] + * @param name a user defined and not necessarily unique name of the element */ Multipole ( int multipole, @@ -48,9 +51,11 @@ namespace impactx amrex::ParticleReal K_skew, amrex::ParticleReal dx = 0, amrex::ParticleReal dy = 0, - amrex::ParticleReal rotation_degree = 0 + amrex::ParticleReal rotation_degree = 0, + std::optional name = std::nullopt ) - : Alignment(dx, dy, rotation_degree), + : Named(name), + Alignment(dx, dy, rotation_degree), m_multipole(multipole), m_Kn(K_normal), m_Ks(K_skew) { // compute factorial of multipole index @@ -98,7 +103,7 @@ namespace impactx // access reference particle values to find (beta*gamma)^2 //amrex::ParticleReal const pt_ref = refpart.pt; - //amrex::ParticleReal const betgam2 = pow(pt_ref, 2) - 1.0_prt; + //amrex::ParticleReal const betgam2 = std::pow(pt_ref, 2) - 1.0_prt; // intialize output values amrex::ParticleReal xout = x; diff --git a/src/particles/elements/NonlinearLens.H b/src/particles/elements/NonlinearLens.H index 3e31fd14b..016d6123a 100644 --- a/src/particles/elements/NonlinearLens.H +++ b/src/particles/elements/NonlinearLens.H @@ -14,6 +14,7 @@ #include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thin.H" +#include "mixin/named.H" #include "mixin/nofinalize.H" #include @@ -25,12 +26,13 @@ namespace impactx { struct NonlinearLens - : public elements::BeamOptic, + : public elements::Named, + public elements::BeamOptic, public elements::Thin, public elements::Alignment, public elements::NoFinalize { - static constexpr auto name = "NonlinearLens"; + static constexpr auto type = "NonlinearLens"; using PType = ImpactXParticleContainer::ParticleType; /** Single short segment of the nonlinear magnetic insert element @@ -45,15 +47,18 @@ namespace impactx * @param dx horizontal translation error in m * @param dy vertical translation error in m * @param rotation_degree rotation error in the transverse plane [degrees] + * @param name a user defined and not necessarily unique name of the element */ NonlinearLens ( amrex::ParticleReal knll, amrex::ParticleReal cnll, amrex::ParticleReal dx = 0, amrex::ParticleReal dy = 0, - amrex::ParticleReal rotation_degree = 0 + amrex::ParticleReal rotation_degree = 0, + std::optional name = std::nullopt ) - : Alignment(dx, dy, rotation_degree), + : Named(name), + Alignment(dx, dy, rotation_degree), m_knll(knll), m_cnll(cnll) { } @@ -95,7 +100,7 @@ namespace impactx // access reference particle values to find (beta*gamma)^2 //amrex::ParticleReal const pt_ref = refpart.pt; - //amrex::ParticleReal const betgam2 = pow(pt_ref, 2) - 1.0_prt; + //amrex::ParticleReal const betgam2 = std::pow(pt_ref, 2) - 1.0_prt; // intialize output values amrex::ParticleReal xout = x; @@ -117,12 +122,12 @@ namespace impactx croot = amrex::sqrt(croot); // compute carcsin = arcsin(zeta) - Complex carcsin = im1*zeta + croot; - carcsin = -im1*amrex::log(carcsin); + Complex carcsin = im1 * zeta + croot; + carcsin = -im1 * amrex::log(carcsin); // compute complex function F'(zeta) - Complex dF = zeta/amrex::pow(croot, 2); - dF = dF + carcsin/amrex::pow(croot,3); + Complex dF = zeta / amrex::pow(croot, 2); + dF = dF + carcsin / amrex::pow(croot, 3); // compute momentum kick amrex::ParticleReal const kick = -m_knll/m_cnll; diff --git a/src/particles/elements/PRot.H b/src/particles/elements/PRot.H index 069be5e50..cbd43118c 100644 --- a/src/particles/elements/PRot.H +++ b/src/particles/elements/PRot.H @@ -13,6 +13,7 @@ #include "particles/ImpactXParticleContainer.H" #include "mixin/beamoptic.H" #include "mixin/thin.H" +#include "mixin/named.H" #include "mixin/nofinalize.H" #include @@ -27,11 +28,12 @@ namespace impactx { struct PRot - : public elements::BeamOptic, + : public elements::Named, + public elements::BeamOptic, public elements::Thin, public elements::NoFinalize { - static constexpr auto name = "PRot"; + static constexpr auto type = "PRot"; using PType = ImpactXParticleContainer::ParticleType; static constexpr amrex::ParticleReal degree2rad = ablastr::constant::math::pi / 180.0; @@ -43,12 +45,16 @@ namespace impactx * * @param phi_in Initial angle of reference trajectory w/r/t z (degrees) * @param phi_out Final angle of reference trajectory w/r/t/ z (degrees) + * @param name a user defined and not necessarily unique name of the element */ PRot ( amrex::ParticleReal phi_in, - amrex::ParticleReal phi_out + amrex::ParticleReal phi_out, + std::optional name = std::nullopt ) - : m_phi_in(phi_in * degree2rad), m_phi_out(phi_out * degree2rad) + : Named(name), + m_phi_in(phi_in * degree2rad), + m_phi_out(phi_out * degree2rad) { } @@ -97,8 +103,8 @@ namespace impactx auto const [sin_theta, cos_theta] = amrex::Math::sincos(theta); auto const [sin_phi_in, cos_phi_in] = amrex::Math::sincos(m_phi_in); - amrex::ParticleReal const pz = sqrt(1.0_prt - 2.0_prt*pt/beta - + pow(pt,2) - pow(py,2) - pow(px + sin_phi_in,2)); + amrex::ParticleReal const pz = std::sqrt(1.0_prt - 2.0_prt*pt/beta + + std::pow(pt,2) - std::pow(py,2) - std::pow(px + sin_phi_in,2)); amrex::ParticleReal const pzf = pz*cos_theta - (px + sin_phi_in)*sin_theta; // advance position and momentum diff --git a/src/particles/elements/Programmable.H b/src/particles/elements/Programmable.H index 37cec4e9b..89b6afa8e 100644 --- a/src/particles/elements/Programmable.H +++ b/src/particles/elements/Programmable.H @@ -10,8 +10,9 @@ #ifndef IMPACTX_ELEMENTS_PROGRAMMABLE_H #define IMPACTX_ELEMENTS_PROGRAMMABLE_H -#include "mixin/thick.H" #include "particles/ImpactXParticleContainer.H" +#include "mixin/named.H" +#include "mixin/thick.H" #include #include @@ -22,24 +23,35 @@ namespace impactx { struct Programmable + : public elements::Named { - static constexpr auto name = "Programmable"; + static constexpr auto type = "Programmable"; using PType = ImpactXParticleContainer::ParticleType; /** This element can be programmed + * + * @param ds Segment length in m + * @param nslice number of slices used for the application of space charge + * @param name a user defined and not necessarily unique name of the element */ - Programmable (amrex::ParticleReal ds=0.0, int nslice=1) - : m_ds(ds), m_nslice(nslice) + Programmable ( + amrex::ParticleReal ds=0.0, + int nslice=1, + std::optional name = std::nullopt + ) + : Named(name), m_ds(ds), m_nslice(nslice) {} /** Push all particles relative to the reference particle * * @param[in,out] pc particle container to push * @param[in] step global step for diagnostics + * @param[in] period for periodic lattices, this is the current period (turn or cycle) */ void operator() ( ImpactXParticleContainer & pc, - int step + int step, + int period ) const; /** Push all particles relative to the reference particle */ @@ -93,7 +105,7 @@ namespace impactx */ bool m_threadsafe = false; - std::function m_push; //! hook for push of whole container + std::function m_push; //! hook for push of whole container (pc, step, period) std::function m_beam_particles; //! hook for beam particles std::function m_ref_particle; //! hook for reference particle std::function m_finalize; //! hook for finalize cleanup diff --git a/src/particles/elements/Programmable.cpp b/src/particles/elements/Programmable.cpp index 3f73ac413..43f37aa73 100644 --- a/src/particles/elements/Programmable.cpp +++ b/src/particles/elements/Programmable.cpp @@ -19,16 +19,17 @@ namespace impactx void Programmable::operator() ( ImpactXParticleContainer & pc, - int step + int step, + int period ) const { if (m_push == nullptr) { // TODO: print if verbose mode is set - push_all(pc, *this, step, m_threadsafe); + push_all(pc, *this, step, period, m_threadsafe); } else { BL_PROFILE("impactx::Push::Programmable"); - m_push(&pc, step); + m_push(&pc, step, period); } } diff --git a/src/particles/elements/Quad.H b/src/particles/elements/Quad.H index b9bcb4e1a..b4178048b 100644 --- a/src/particles/elements/Quad.H +++ b/src/particles/elements/Quad.H @@ -14,6 +14,7 @@ #include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thick.H" +#include "mixin/named.H" #include "mixin/nofinalize.H" #include @@ -25,12 +26,13 @@ namespace impactx { struct Quad - : public elements::BeamOptic, + : public elements::Named, + public elements::BeamOptic, public elements::Thick, public elements::Alignment, public elements::NoFinalize { - static constexpr auto name = "Quad"; + static constexpr auto type = "Quad"; using PType = ImpactXParticleContainer::ParticleType; /** A Quadrupole magnet @@ -44,6 +46,7 @@ namespace impactx * @param dy vertical translation error in m * @param rotation_degree rotation error in the transverse plane [degrees] * @param nslice number of slices used for the application of space charge + * @param name a user defined and not necessarily unique name of the element */ Quad ( amrex::ParticleReal ds, @@ -51,9 +54,11 @@ namespace impactx amrex::ParticleReal dx = 0, amrex::ParticleReal dy = 0, amrex::ParticleReal rotation_degree = 0, - int nslice = 1 + int nslice = 1, + std::optional name = std::nullopt ) - : Thick(ds, nslice), + : Named(name), + Thick(ds, nslice), Alignment(dx, dy, rotation_degree), m_k(k) { diff --git a/src/particles/elements/RFCavity.H b/src/particles/elements/RFCavity.H index f0337aed3..e4aae613e 100644 --- a/src/particles/elements/RFCavity.H +++ b/src/particles/elements/RFCavity.H @@ -14,6 +14,7 @@ #include "particles/integrators/Integrators.H" #include "mixin/alignment.H" #include "mixin/beamoptic.H" +#include "mixin/named.H" #include "mixin/thick.H" #include @@ -102,11 +103,12 @@ namespace RFCavityData } // namespace RFCavityData struct RFCavity - : public elements::BeamOptic, + : public elements::Named, + public elements::BeamOptic, public elements::Thick, public elements::Alignment { - static constexpr auto name = "RFCavity"; + static constexpr auto type = "RFCavity"; using PType = ImpactXParticleContainer::ParticleType; /** An RF cavity @@ -123,6 +125,7 @@ namespace RFCavityData * @param mapsteps number of integration steps per slice used for * map and reference particle push in applied fields * @param nslice number of slices used for the application of space charge + * @param name a user defined and not necessarily unique name of the element */ RFCavity ( amrex::ParticleReal ds, @@ -135,9 +138,11 @@ namespace RFCavityData amrex::ParticleReal dy = 0, amrex::ParticleReal rotation_degree = 0, int mapsteps = 1, - int nslice = 1 + int nslice = 1, + std::optional name = std::nullopt ) - : Thick(ds, nslice), + : Named(name), + Thick(ds, nslice), Alignment(dx, dy, rotation_degree), m_escale(escale), m_freq(freq), m_phase(phase), m_mapsteps(mapsteps) { @@ -282,7 +287,7 @@ namespace RFCavityData amrex::ParticleReal const slice_ds = m_ds / nslice(); // compute intial value of beta*gamma - amrex::ParticleReal const bgi = sqrt(pow(pt, 2) - 1.0_prt); + amrex::ParticleReal const bgi = std::sqrt(std::pow(pt, 2) - 1.0_prt); // call integrator to advance (t,pt) amrex::ParticleReal const zin = s - sedge; @@ -298,7 +303,7 @@ namespace RFCavityData refpart.z = z + slice_ds*pz/bgi; // compute final value of beta*gamma - amrex::ParticleReal const bgf = sqrt(pow(ptf, 2) - 1.0_prt); + amrex::ParticleReal const bgf = std::sqrt(std::pow(ptf, 2) - 1.0_prt); // advance momentum (px,py,pz) refpart.px = px*bgf/bgi; @@ -367,14 +372,14 @@ namespace RFCavityData efieldint = z*efield; for (int j=1; j < m_ncoef; ++j) { - efield = efield + cos_data[j]*cos(j*2*pi*z/zlen) + - sin_data[j]*sin(j*2*pi*z/zlen); - efieldp = efieldp-j*2*pi*cos_data[j]*sin(j*2*pi*z/zlen)/zlen + - j*2*pi*sin_data[j]*cos(j*2*pi*z/zlen)/zlen; - efieldpp = efieldpp- pow(j*2*pi*cos_data[j]/zlen,2) *cos(j*2*pi*z/zlen) - - pow(j*2*pi*sin_data[j]/zlen,2) *sin(j*2*pi*z/zlen); - efieldint = efieldint + zlen*cos_data[j]*sin(j*2*pi*z/zlen)/(j*2*pi) - - zlen*sin_data[j]*cos(j*2*pi*z/zlen)/(j*2*pi); + efield = efield + cos_data[j] * std::cos(j*2*pi*z/zlen) + + sin_data[j] * std::sin(j*2*pi*z/zlen); + efieldp = efieldp-j*2*pi*cos_data[j] * std::sin(j*2*pi*z/zlen)/zlen + + j*2*pi*sin_data[j] * std::cos(j*2*pi*z/zlen)/zlen; + efieldpp = efieldpp- std::pow(j*2*pi*cos_data[j]/zlen,2) * std::cos(j*2*pi*z/zlen) - + std::pow(j*2*pi*sin_data[j]/zlen,2) * std::sin(j*2*pi*z/zlen); + efieldint = efieldint + zlen*cos_data[j] * std::sin(j*2*pi*z/zlen)/(j*2*pi) - + zlen*sin_data[j] * std::cos(j*2*pi*z/zlen)/(j*2*pi); } } else // endpoint of the RF, outsize zlen @@ -382,7 +387,7 @@ namespace RFCavityData efieldint = std::copysign(z, z)*zmid*0.5_prt*cos_data[0];; for (int j=1; j < m_ncoef; ++j) { - efieldint = efieldint - zlen*sin_data[j]*cos(j*pi)/(j*2*pi); + efieldint = efieldint - zlen*sin_data[j] * std::cos(j*pi)/(j*2*pi); } } return std::make_tuple(efield, efieldp, efieldint); @@ -408,7 +413,7 @@ namespace RFCavityData amrex::ParticleReal const pt = refpart.pt; if (pt < -1.0_prt) { - refpart.t = t + tau/sqrt(1.0_prt - pow(pt, -2)); + refpart.t = t + tau/std::sqrt(1.0_prt - std::pow(pt, -2)); refpart.pt = pt; } else { @@ -459,17 +464,17 @@ namespace RFCavityData // push the linear map equations amrex::Array2D const R = refpart.map; amrex::ParticleReal const s = tau/refpart.beta_gamma(); - amrex::ParticleReal const L = E0*ezp*sin(k*t+phi)/(2.0_prt*k); + amrex::ParticleReal const L = E0*ezp * std::sin(k*t+phi)/(2.0_prt*k); refpart.map(1,1) = (1.0_prt-s*L)*R(1,1) + s*R(2,1); refpart.map(1,2) = (1.0_prt-s*L)*R(1,2) + s*R(2,2); - refpart.map(2,1) = -s*pow(L,2)*R(1,1) + (1.0_prt+s*L)*R(2,1); - refpart.map(2,2) = -s*pow(L,2)*R(1,2) + (1.0_prt+s*L)*R(2,2); + refpart.map(2,1) = -s * std::pow(L,2)*R(1,1) + (1.0_prt+s*L)*R(2,1); + refpart.map(2,2) = -s * std::pow(L,2)*R(1,2) + (1.0_prt+s*L)*R(2,2); refpart.map(3,3) = (1.0_prt-s*L)*R(3,3) + s*R(4,3); refpart.map(3,4) = (1.0_prt-s*L)*R(3,4) + s*R(4,4); - refpart.map(4,3) = -s*pow(L,2)*R(3,3) + (1.0_prt+s*L)*R(4,3); - refpart.map(4,4) = -s*pow(L,2)*R(3,4) + (1.0_prt+s*L)*R(4,4); + refpart.map(4,3) = -s * std::pow(L,2)*R(3,3) + (1.0_prt+s*L)*R(4,3); + refpart.map(4,4) = -s * std::pow(L,2)*R(3,4) + (1.0_prt+s*L)*R(4,4); } /** This pushes the reference particle and the linear map matrix @@ -506,12 +511,12 @@ namespace RFCavityData amrex::ignore_unused(ezf); refpart.t = t; - refpart.pt = pt - E0*(ezintf-ezint)*cos(k*t+phi); + refpart.pt = pt - E0*(ezintf-ezint) * std::cos(k*t+phi); // push the linear map equations amrex::Array2D const R = refpart.map; - amrex::ParticleReal const M = E0*(ezintf-ezint)*k*sin(k*t+phi); - amrex::ParticleReal const L = E0*(ezpf-ezp)*sin(k*t+phi)/(2.0_prt*k)+M/2.0_prt; + amrex::ParticleReal const M = E0*(ezintf-ezint)*k * std::sin(k*t+phi); + amrex::ParticleReal const L = E0*(ezpf-ezp) * std::sin(k*t+phi)/(2.0_prt*k)+M/2.0_prt; refpart.map(2,1) = L*R(1,1) + R(2,1); refpart.map(2,2) = L*R(1,2) + R(2,2); diff --git a/src/particles/elements/Sbend.H b/src/particles/elements/Sbend.H index 9459e3343..5fa7c0080 100644 --- a/src/particles/elements/Sbend.H +++ b/src/particles/elements/Sbend.H @@ -14,6 +14,7 @@ #include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thick.H" +#include "mixin/named.H" #include "mixin/nofinalize.H" #include @@ -26,12 +27,13 @@ namespace impactx { struct Sbend - : public elements::BeamOptic, + : public elements::Named, + public elements::BeamOptic, public elements::Thick, public elements::Alignment, public elements::NoFinalize { - static constexpr auto name = "Sbend"; + static constexpr auto type = "Sbend"; using PType = ImpactXParticleContainer::ParticleType; /** An ideal sector bend @@ -42,6 +44,7 @@ namespace impactx * @param dy vertical translation error in m * @param rotation_degree rotation error in the transverse plane [degrees] * @param nslice number of slices used for the application of space charge + * @param name a user defined and not necessarily unique name of the element */ Sbend ( amrex::ParticleReal ds, @@ -49,9 +52,11 @@ namespace impactx amrex::ParticleReal dx = 0, amrex::ParticleReal dy = 0, amrex::ParticleReal rotation_degree = 0, - int nslice = 1 + int nslice = 1, + std::optional name = std::nullopt ) - : Thick(ds, nslice), + : Named(name), + Thick(ds, nslice), Alignment(dx, dy, rotation_degree), m_rc(rc) { @@ -103,8 +108,8 @@ namespace impactx // access reference particle values to find beta*gamma^2 amrex::ParticleReal const pt_ref = refpart.pt; - amrex::ParticleReal const betgam2 = pow(pt_ref, 2) - 1.0_prt; - amrex::ParticleReal const bet = sqrt(betgam2/(1.0_prt + betgam2)); + amrex::ParticleReal const betgam2 = std::pow(pt_ref, 2) - 1.0_prt; + amrex::ParticleReal const bet = std::sqrt(betgam2/(1.0_prt + betgam2)); // calculate expensive terms once amrex::ParticleReal const theta = slice_ds/m_rc; @@ -162,7 +167,7 @@ namespace impactx // assign intermediate parameter amrex::ParticleReal const theta = slice_ds/m_rc; - amrex::ParticleReal const B = sqrt(pow(pt,2)-1.0_prt)/m_rc; + amrex::ParticleReal const B = std::sqrt(std::pow(pt,2)-1.0_prt)/m_rc; // calculate expensive terms once auto const [sin_theta, cos_theta] = amrex::Math::sincos(theta); diff --git a/src/particles/elements/ShortRF.H b/src/particles/elements/ShortRF.H index cffcd2469..fb8a4dcbd 100644 --- a/src/particles/elements/ShortRF.H +++ b/src/particles/elements/ShortRF.H @@ -14,6 +14,7 @@ #include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thin.H" +#include "mixin/named.H" #include "mixin/nofinalize.H" #include @@ -25,12 +26,13 @@ namespace impactx { struct ShortRF - : public elements::BeamOptic, + : public elements::Named, + public elements::BeamOptic, public elements::Thin, public elements::Alignment, public elements::NoFinalize { - static constexpr auto name = "ShortRF"; + static constexpr auto type = "ShortRF"; using PType = ImpactXParticleContainer::ParticleType; /** A short RF cavity element @@ -44,6 +46,7 @@ namespace impactx * @param dx horizontal translation error in m * @param dy vertical translation error in m * @param rotation_degree rotation error in the transverse plane [degrees] + * @param name a user defined and not necessarily unique name of the element */ ShortRF ( amrex::ParticleReal V, @@ -51,9 +54,11 @@ namespace impactx amrex::ParticleReal phase, amrex::ParticleReal dx = 0, amrex::ParticleReal dy = 0, - amrex::ParticleReal rotation_degree = 0 + amrex::ParticleReal rotation_degree = 0, + std::optional name = std::nullopt ) - : Alignment(dx, dy, rotation_degree), + : Named(name), + Alignment(dx, dy, rotation_degree), m_V(V), m_freq(freq), m_phase(phase) { } @@ -99,9 +104,9 @@ namespace impactx // access reference particle values (final, initial): amrex::ParticleReal const ptf_ref = refpart.pt; - amrex::ParticleReal const pti_ref = ptf_ref + m_V*cos(phi); - amrex::ParticleReal const bgf = sqrt(pow(ptf_ref, 2) - 1.0_prt); - amrex::ParticleReal const bgi = sqrt(pow(pti_ref, 2) - 1.0_prt); + amrex::ParticleReal const pti_ref = ptf_ref + m_V * std::cos(phi); + amrex::ParticleReal const bgf = std::sqrt(std::pow(ptf_ref, 2) - 1.0_prt); + amrex::ParticleReal const bgi = std::sqrt(std::pow(pti_ref, 2) - 1.0_prt); // initial conversion from static to dynamic units: px = px*bgi; @@ -124,7 +129,7 @@ namespace impactx pyout = py; // tout = t; - ptout = pt - m_V*cos(k*t + phi) + m_V*cos(phi); + ptout = pt - m_V * std::cos(k*t + phi) + m_V * std::cos(phi); // assign updated values x = xout; @@ -170,14 +175,14 @@ namespace impactx amrex::ParticleReal const phi = m_phase*(pi/180.0_prt); // compute intial value of beta*gamma - amrex::ParticleReal const bgi = sqrt(pow(pt, 2) - 1.0_prt); + amrex::ParticleReal const bgi = std::sqrt(std::pow(pt, 2) - 1.0_prt); // advance pt - refpart.pt = pt - m_V*cos(phi); + refpart.pt = pt - m_V * std::cos(phi); // compute final value of beta*gamma amrex::ParticleReal const ptf = refpart.pt; - amrex::ParticleReal const bgf = sqrt(pow(ptf, 2) - 1.0_prt); + amrex::ParticleReal const bgf = std::sqrt(std::pow(ptf, 2) - 1.0_prt); // advance position (x,y,z,t) refpart.x = x; diff --git a/src/particles/elements/SoftQuad.H b/src/particles/elements/SoftQuad.H index 43c6c9c53..2637d638e 100644 --- a/src/particles/elements/SoftQuad.H +++ b/src/particles/elements/SoftQuad.H @@ -14,6 +14,7 @@ #include "particles/integrators/Integrators.H" #include "mixin/alignment.H" #include "mixin/beamoptic.H" +#include "mixin/named.H" #include "mixin/thick.H" #include @@ -111,11 +112,12 @@ namespace SoftQuadrupoleData } // namespace SoftQuadrupoleData struct SoftQuadrupole - : public elements::BeamOptic, + : public elements::Named, + public elements::BeamOptic, public elements::Thick, public elements::Alignment { - static constexpr auto name = "SoftQuadrupole"; + static constexpr auto type = "SoftQuadrupole"; using PType = ImpactXParticleContainer::ParticleType; /** A soft-edge quadrupole @@ -130,6 +132,7 @@ namespace SoftQuadrupoleData * @param mapsteps number of integration steps per slice used for * map and reference particle push in applied fields * @param nslice number of slices used for the application of space charge + * @param name a user defined and not necessarily unique name of the element */ SoftQuadrupole ( amrex::ParticleReal ds, @@ -140,9 +143,11 @@ namespace SoftQuadrupoleData amrex::ParticleReal dy = 0, amrex::ParticleReal rotation_degree = 0, int mapsteps = 1, - int nslice = 1 + int nslice = 1, + std::optional name = std::nullopt ) - : Thick(ds, nslice), + : Named(name), + Thick(ds, nslice), Alignment(dx, dy, rotation_degree), m_gscale(gscale), m_mapsteps(mapsteps), m_id(SoftQuadrupoleData::next_id) { @@ -285,7 +290,7 @@ namespace SoftQuadrupoleData amrex::ParticleReal const slice_ds = m_ds / nslice(); // compute intial value of beta*gamma - amrex::ParticleReal const bgi = sqrt(pow(pt, 2) - 1.0_prt); + amrex::ParticleReal const bgi = std::sqrt(std::pow(pt, 2) - 1.0_prt); // call integrator to advance (t,pt) amrex::ParticleReal const zin = s - sedge; @@ -312,7 +317,7 @@ namespace SoftQuadrupoleData refpart.z = z + slice_ds*pz/bgi; // compute final value of beta*gamma - amrex::ParticleReal const bgf = sqrt(pow(ptf, 2) - 1.0_prt); + amrex::ParticleReal const bgf = std::sqrt(std::pow(ptf, 2) - 1.0_prt); // advance momentum (px,py,pz) refpart.px = px*bgf/bgi; @@ -362,12 +367,12 @@ namespace SoftQuadrupoleData bfieldint = z*bfield; for (int j=1; j < m_ncoef; ++j) { - bfield = bfield + cos_data[j] * cos(j * 2 * pi * z / zlen) + - sin_data[j] * sin(j * 2 * pi * z / zlen); - bfieldp = bfieldp - j * 2 * pi * cos_data[j] * sin(j * 2 * pi * z / zlen) / zlen + - j * 2 * pi * sin_data[j] * cos(j * 2 * pi * z / zlen) / zlen; - bfieldint = bfieldint + zlen * cos_data[j] * sin(j * 2 * pi * z / zlen) / (j * 2 * pi) - - zlen * sin_data[j] * cos(j * 2 * pi * z / zlen) / (j * 2 * pi); + bfield = bfield + cos_data[j] *std::cos(j * 2 * pi * z / zlen) + + sin_data[j] *std::sin(j * 2 * pi * z / zlen); + bfieldp = bfieldp - j * 2 * pi * cos_data[j] *std::sin(j * 2 * pi * z / zlen) / zlen + + j * 2 * pi * sin_data[j] *std::cos(j * 2 * pi * z / zlen) / zlen; + bfieldint = bfieldint + zlen * cos_data[j] *std::sin(j * 2 * pi * z / zlen) / (j * 2 * pi) - + zlen * sin_data[j] *std::cos(j * 2 * pi * z / zlen) / (j * 2 * pi); } } return std::make_tuple(bfield, bfieldp, bfieldint); @@ -394,7 +399,7 @@ namespace SoftQuadrupoleData amrex::ParticleReal const z = zeval; if (pt < -1.0_prt) { - refpart.t = t + tau/sqrt(1.0_prt - pow(pt, -2)); + refpart.t = t + tau/std::sqrt(1.0_prt - std::pow(pt, -2)); refpart.pt = pt; } else { diff --git a/src/particles/elements/SoftSol.H b/src/particles/elements/SoftSol.H index 3726313d6..f5263fe20 100644 --- a/src/particles/elements/SoftSol.H +++ b/src/particles/elements/SoftSol.H @@ -14,6 +14,7 @@ #include "particles/integrators/Integrators.H" #include "mixin/alignment.H" #include "mixin/beamoptic.H" +#include "mixin/named.H" #include "mixin/thick.H" #include @@ -116,11 +117,12 @@ namespace SoftSolenoidData } // namespace SoftSolenoidData struct SoftSolenoid - : public elements::BeamOptic, + : public elements::Named, + public elements::BeamOptic, public elements::Thick, public elements::Alignment { - static constexpr auto name = "SoftSolenoid"; + static constexpr auto type = "SoftSolenoid"; using PType = ImpactXParticleContainer::ParticleType; /** A soft-edge solenoid @@ -140,6 +142,7 @@ namespace SoftSolenoidData * @param mapsteps number of integration steps per slice used for * map and reference particle push in applied fields * @param nslice number of slices used for the application of space charge + * @param name a user defined and not necessarily unique name of the element */ SoftSolenoid ( amrex::ParticleReal ds, @@ -151,9 +154,11 @@ namespace SoftSolenoidData amrex::ParticleReal dy = 0, amrex::ParticleReal rotation_degree = 0, int mapsteps = 1, - int nslice = 1 + int nslice = 1, + std::optional name = std::nullopt ) - : Thick(ds, nslice), + : Named(name), + Thick(ds, nslice), Alignment(dx, dy, rotation_degree), m_bscale(bscale), m_unit(unit), m_mapsteps(mapsteps), m_id(SoftSolenoidData::next_id) { @@ -296,7 +301,7 @@ namespace SoftSolenoidData amrex::ParticleReal const slice_ds = m_ds / nslice(); // compute intial value of beta*gamma - amrex::ParticleReal const bgi = sqrt(pow(pt, 2) - 1.0_prt); + amrex::ParticleReal const bgi = std::sqrt(std::pow(pt, 2) - 1.0_prt); // call integrator to advance (t,pt) amrex::ParticleReal const zin = s - sedge; @@ -321,7 +326,7 @@ namespace SoftSolenoidData refpart.z = z + slice_ds*pz/bgi; // compute final value of beta*gamma - amrex::ParticleReal const bgf = sqrt(pow(ptf, 2) - 1.0_prt); + amrex::ParticleReal const bgf = std::sqrt(std::pow(ptf, 2) - 1.0_prt); // advance momentum (px,py,pz) refpart.px = px*bgf/bgi; @@ -371,12 +376,12 @@ namespace SoftSolenoidData bfieldint = z*bfield; for (int j=1; j < m_ncoef; ++j) { - bfield = bfield + cos_data[j]*cos(j*2*pi*z/zlen) + - sin_data[j]*sin(j*2*pi*z/zlen); - bfieldp = bfieldp-j*2*pi*cos_data[j]*sin(j*2*pi*z/zlen)/zlen + - j*2*pi*sin_data[j]*cos(j*2*pi*z/zlen)/zlen; - bfieldint = bfieldint + zlen*cos_data[j]*sin(j*2*pi*z/zlen)/(j*2*pi) - - zlen*sin_data[j]*cos(j*2*pi*z/zlen)/(j*2*pi); + bfield = bfield + cos_data[j] * std::cos(j*2*pi*z/zlen) + + sin_data[j] * std::sin(j*2*pi*z/zlen); + bfieldp = bfieldp-j*2*pi*cos_data[j] * std::sin(j*2*pi*z/zlen)/zlen + + j*2*pi*sin_data[j] * std::cos(j*2*pi*z/zlen)/zlen; + bfieldint = bfieldint + zlen*cos_data[j] * std::sin(j*2*pi*z/zlen)/(j*2*pi) - + zlen*sin_data[j] * std::cos(j*2*pi*z/zlen)/(j*2*pi); } } return std::make_tuple(bfield, bfieldp, bfieldint); @@ -403,7 +408,7 @@ namespace SoftSolenoidData amrex::ParticleReal const z = zeval; if (pt < -1.0_prt) { - refpart.t = t + tau/sqrt(1.0_prt - pow(pt, -2)); + refpart.t = t + tau/std::sqrt(1.0_prt - std::pow(pt, -2)); refpart.pt = pt; } else { @@ -466,7 +471,7 @@ namespace SoftSolenoidData // push the linear map equations amrex::Array2D const R = refpart.map; amrex::ParticleReal const alpha = B0*bz/2.0_prt; - amrex::ParticleReal const alpha2 = pow(alpha,2); + amrex::ParticleReal const alpha2 = std::pow(alpha,2); refpart.map(2,1) = R(2,1) - tau*alpha2*R(1,1); refpart.map(2,2) = R(2,2) - tau*alpha2*R(1,2); @@ -515,8 +520,8 @@ namespace SoftSolenoidData // push the linear map equations amrex::Array2D const R = refpart.map; amrex::ParticleReal const theta = tau*B0*bz/2.0_prt; - amrex::ParticleReal const cs = cos(theta); - amrex::ParticleReal const sn = sin(theta); + amrex::ParticleReal const cs = std::cos(theta); + amrex::ParticleReal const sn = std::sin(theta); refpart.map(1,1) = R(1,1)*cs + R(3,1)*sn; refpart.map(1,2) = R(1,2)*cs + R(3,2)*sn; diff --git a/src/particles/elements/Sol.H b/src/particles/elements/Sol.H index 7eeb62d0b..608f0d9ad 100644 --- a/src/particles/elements/Sol.H +++ b/src/particles/elements/Sol.H @@ -14,6 +14,7 @@ #include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thick.H" +#include "mixin/named.H" #include "mixin/nofinalize.H" #include @@ -26,12 +27,13 @@ namespace impactx { struct Sol - : public elements::BeamOptic, + : public elements::Named, + public elements::BeamOptic, public elements::Thick, public elements::Alignment, public elements::NoFinalize { - static constexpr auto name = "Sol"; + static constexpr auto type = "Sol"; using PType = ImpactXParticleContainer::ParticleType; /** An ideal hard-edge Solenoid magnet @@ -43,6 +45,7 @@ namespace impactx * @param dy vertical translation error in m * @param rotation_degree rotation error in the transverse plane [degrees] * @param nslice number of slices used for the application of space charge + * @param name a user defined and not necessarily unique name of the element */ Sol ( amrex::ParticleReal ds, @@ -50,9 +53,11 @@ namespace impactx amrex::ParticleReal dx = 0, amrex::ParticleReal dy = 0, amrex::ParticleReal rotation_degree = 0, - int nslice = 1 + int nslice = 1, + std::optional name = std::nullopt ) - : Thick(ds, nslice), + : Named(name), + Thick(ds, nslice), Alignment(dx, dy, rotation_degree), m_ks(ks) { @@ -94,7 +99,7 @@ namespace impactx // access reference particle values to find beta*gamma^2 amrex::ParticleReal const pt_ref = refpart.pt; - amrex::ParticleReal const betgam2 = pow(pt_ref, 2) - 1.0_prt; + amrex::ParticleReal const betgam2 = std::pow(pt_ref, 2) - 1.0_prt; // compute phase advance per unit length (in rad/m) and // rotation angle (in rad) @@ -168,7 +173,7 @@ namespace impactx amrex::ParticleReal const slice_ds = m_ds / nslice(); // assign intermediate parameter - amrex::ParticleReal const step = slice_ds / sqrt(pow(pt,2)-1.0_prt); + amrex::ParticleReal const step = slice_ds / std::sqrt(std::pow(pt,2) - 1.0_prt); // advance position and momentum (straight element) refpart.x = x + step*px; diff --git a/src/particles/elements/TaperedPL.H b/src/particles/elements/TaperedPL.H index 261dcff55..9a107679e 100644 --- a/src/particles/elements/TaperedPL.H +++ b/src/particles/elements/TaperedPL.H @@ -14,6 +14,7 @@ #include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thin.H" +#include "mixin/named.H" #include "mixin/nofinalize.H" #include @@ -25,12 +26,13 @@ namespace impactx { struct TaperedPL - : public elements::BeamOptic, + : public elements::Named, + public elements::BeamOptic, public elements::Thin, public elements::Alignment, public elements::NoFinalize { - static constexpr auto name = "TaperedPL"; + static constexpr auto type = "TaperedPL"; using PType = ImpactXParticleContainer::ParticleType; /** A short segment of a nonlinear plasma lens with a transverse taper. @@ -46,6 +48,7 @@ namespace impactx * @param dx horizontal translation error in m * @param dy vertical translation error in m * @param rotation_degree rotation error in the transverse plane [degrees] + * @param name a user defined and not necessarily unique name of the element */ TaperedPL ( amrex::ParticleReal k, @@ -53,9 +56,11 @@ namespace impactx int unit, amrex::ParticleReal dx = 0, amrex::ParticleReal dy = 0, - amrex::ParticleReal rotation_degree = 0 + amrex::ParticleReal rotation_degree = 0, + std::optional name = std::nullopt ) - : Alignment(dx, dy, rotation_degree), + : Named(name), + Alignment(dx, dy, rotation_degree), m_k(k), m_taper(taper), m_unit(unit) { } diff --git a/src/particles/elements/ThinDipole.H b/src/particles/elements/ThinDipole.H index 2bfaa74be..84bcc57e5 100644 --- a/src/particles/elements/ThinDipole.H +++ b/src/particles/elements/ThinDipole.H @@ -14,20 +14,23 @@ #include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thin.H" +#include "mixin/named.H" #include "mixin/nofinalize.H" #include #include + namespace impactx { struct ThinDipole - : public elements::BeamOptic, + : public elements::Named, + public elements::BeamOptic, public elements::Thin, public elements::Alignment, public elements::NoFinalize { - static constexpr auto name = "ThinDipole"; + static constexpr auto type = "ThinDipole"; using PType = ImpactXParticleContainer::ParticleType; static constexpr amrex::ParticleReal degree2rad = ablastr::constant::math::pi / 180.0; @@ -44,15 +47,18 @@ namespace impactx * @param dx horizontal translation error in m * @param dy vertical translation error in m * @param rotation_degree rotation error in the transverse plane [degrees] + * @param name a user defined and not necessarily unique name of the element */ ThinDipole ( amrex::ParticleReal theta, amrex::ParticleReal rc, amrex::ParticleReal dx = 0, amrex::ParticleReal dy = 0, - amrex::ParticleReal rotation_degree = 0 + amrex::ParticleReal rotation_degree = 0, + std::optional name = std::nullopt ) - : Alignment(dx, dy, rotation_degree), + : Named(name), + Alignment(dx, dy, rotation_degree), m_theta(theta * degree2rad), m_rc(rc) { @@ -104,7 +110,7 @@ namespace impactx amrex::ParticleReal ptout = pt; // compute the function expressing dp/p in terms of pt (labeled f in Ripken etc.) - amrex::ParticleReal f = -1.0_prt + sqrt(1.0_prt - 2.0_prt*pt/beta_ref + pow(pt,2)); + amrex::ParticleReal f = -1.0_prt + std::sqrt(1.0_prt - 2.0_prt*pt/beta_ref + std::pow(pt,2)); amrex::ParticleReal fprime = (1.0_prt - beta_ref*pt)/(beta_ref*(1.0_prt + f)); // compute the effective (equivalent) arc length and curvature @@ -113,7 +119,7 @@ namespace impactx // advance position and momentum x = xout; - pxout = px - pow(kx,2) * ds * xout + kx * ds * f; //eq (3.2b) + pxout = px - std::pow(kx,2) * ds * xout + kx * ds * f; //eq (3.2b) y = yout; pyout = py; diff --git a/src/particles/elements/diagnostics/AdditionalProperties.cpp b/src/particles/elements/diagnostics/AdditionalProperties.cpp index 2c6d348de..a6c9ca29b 100644 --- a/src/particles/elements/diagnostics/AdditionalProperties.cpp +++ b/src/particles/elements/diagnostics/AdditionalProperties.cpp @@ -53,7 +53,7 @@ namespace impactx::diagnostics NonlinearLensInvariants const nonlinear_lens_invariants(alpha, beta, tn, cn); // profile time spent here - std::string profile_name = "impactx::Push::" + std::string(BeamMonitor::name) + "::add_optional_properties"; + std::string profile_name = "impactx::Push::" + std::string(BeamMonitor::type) + "::add_optional_properties"; BL_PROFILE(profile_name); // add runtime properties for H and I diff --git a/src/particles/elements/diagnostics/openPMD.H b/src/particles/elements/diagnostics/openPMD.H index 04eb1a925..f5ee0d4a8 100644 --- a/src/particles/elements/diagnostics/openPMD.H +++ b/src/particles/elements/diagnostics/openPMD.H @@ -65,7 +65,7 @@ namespace detail struct BeamMonitor : public elements::Thin { - static constexpr auto name = "BeamMonitor"; + static constexpr auto type = "BeamMonitor"; using PType = typename ImpactXParticleContainer::ParticleType; using PinnedContainer = typename ImpactXParticleContainer::ContainerLike; @@ -76,8 +76,9 @@ namespace detail * @param series_name name of the data series, usually the element name * @param backend file format backend for openPMD, e.g., "bp" or "h5" * @param encoding openPMD iteration encoding: "v"ariable based, "f"ile based, "g"roup based (default) + * @param period_sample_intervals for periodic lattice, only output every Nth period (turn) */ - BeamMonitor (std::string series_name, std::string backend="default", std::string encoding="g"); + BeamMonitor (std::string series_name, std::string backend="default", std::string encoding="g", int period_sample_intervals=1); BeamMonitor (BeamMonitor const & other) = default; BeamMonitor (BeamMonitor && other) = default; @@ -108,10 +109,12 @@ namespace detail * * @param[in,out] pc particle container to push * @param[in] step global step for diagnostics + * @param[in] period for periodic lattices, this is the current period (turn or cycle) */ void operator() ( ImpactXParticleContainer & pc, - int step + int step, + int period ); /** Write a tile of particles @@ -156,6 +159,8 @@ namespace detail int m_file_min_digits = 6; //! minimum number of digits to iteration number in file name + int m_period_sample_intervals = 1; //! only output every Nth period (turn or cycle) of periodic lattices + /** This rank's offset in the MPI-global particle array, by level * * This MUST be updated by prepare() before the next step's output. diff --git a/src/particles/elements/diagnostics/openPMD.cpp b/src/particles/elements/diagnostics/openPMD.cpp index db35bcbab..8b215d3e2 100644 --- a/src/particles/elements/diagnostics/openPMD.cpp +++ b/src/particles/elements/diagnostics/openPMD.cpp @@ -154,8 +154,8 @@ namespace detail #endif // ImpactX_USE_OPENPMD } - BeamMonitor::BeamMonitor (std::string series_name, std::string backend, std::string encoding) : - m_series_name(std::move(series_name)), m_OpenPMDFileType(std::move(backend)) + BeamMonitor::BeamMonitor (std::string series_name, std::string backend, std::string encoding, int period_sample_intervals) : + m_series_name(std::move(series_name)), m_OpenPMDFileType(std::move(backend)), m_period_sample_intervals(period_sample_intervals) { #ifdef ImpactX_USE_OPENPMD // pick first available backend if default is chosen @@ -179,8 +179,10 @@ namespace detail else if ( "f" == encoding ) series_encoding = openPMD::IterationEncoding::fileBased; - // legacy options from other diagnostics amrex::ParmParse pp_diag("diag"); + // turn filter + pp_diag.queryAdd("period_sample_intervals", m_period_sample_intervals); + // legacy options from other diagnostics pp_diag.queryAdd("file_min_digits", m_file_min_digits); // Ensure m_series is the same for the same names. @@ -311,11 +313,16 @@ namespace detail void BeamMonitor::operator() ( ImpactXParticleContainer & pc, - int step + int step, + int period ) { + // filter out this turn? + if (period % m_period_sample_intervals != 0) + return; + #ifdef ImpactX_USE_OPENPMD - std::string profile_name = "impactx::Push::" + std::string(BeamMonitor::name); + std::string profile_name = "impactx::Push::" + std::string(BeamMonitor::type); BL_PROFILE(profile_name); // preparing to access reference particle data: RefPart diff --git a/src/particles/elements/mixin/beamoptic.H b/src/particles/elements/mixin/beamoptic.H index fd58ac600..0616b5374 100644 --- a/src/particles/elements/mixin/beamoptic.H +++ b/src/particles/elements/mixin/beamoptic.H @@ -147,10 +147,16 @@ namespace detail template struct BeamOptic { - /** Push first the reference particle, then all other particles */ + /** Push first the reference particle, then all other particles + * + * @param[inout] pc container of the particles to push + * @param[in] step global step for diagnostics + * @param[in] period for periodic lattices, this is the current period (turn or cycle) + */ void operator() ( ImpactXParticleContainer & pc, - int step + int step, + int period ) { static_assert( @@ -159,7 +165,7 @@ namespace detail ); T_Element& element = *static_cast(this); - push_all(pc, element, step); + push_all(pc, element, step, period); } /** This pushes the particles on a particle iterator tile or box. diff --git a/src/particles/elements/mixin/named.H b/src/particles/elements/mixin/named.H new file mode 100644 index 000000000..f06a6d86c --- /dev/null +++ b/src/particles/elements/mixin/named.H @@ -0,0 +1,153 @@ +/* Copyright 2022-2024 The Regents of the University of California, through Lawrence + * Berkeley National Laboratory (subject to receipt of any required + * approvals from the U.S. Dept. of Energy). All rights reserved. + * + * This file is part of ImpactX. + * + * Authors: Axel Huebl + * License: BSD-3-Clause-LBNL + */ +#ifndef IMPACTX_ELEMENTS_MIXIN_NAMED_H +#define IMPACTX_ELEMENTS_MIXIN_NAMED_H + +#include "particles/ImpactXParticleContainer.H" + +#include +#include + +#include +#include +#include +#include + + +namespace impactx::elements +{ + /** This is a helper class for lattice elements with a user-defined name + */ + struct Named + { + /** Overwrite the name of the element + * + * @param new_name set a new name on the element + */ + AMREX_GPU_HOST + void set_name ( + std::string const & new_name + ) + { + // free old name + if (m_name != nullptr) { + delete[] m_name; + m_name = nullptr; + } + + // set new name + if (new_name.size() > 0) { + m_name = new char[new_name.size() + 1]; + std::strcpy(m_name, new_name.c_str()); + } + } + + /** A user-named element + * + * @param name a user defined and not necessarily unique name of the element + */ + AMREX_GPU_HOST + Named ( + std::optional name + ) + { + if (name.has_value()) { + set_name(*name); + } + } + + AMREX_GPU_HOST_DEVICE + ~Named () + { + AMREX_IF_ON_HOST(( + if (m_name != nullptr) { + delete[] m_name; + m_name = nullptr; + } + )) + } + + AMREX_GPU_HOST + Named (Named const & other) + { + if (other.has_name()) { + m_name = new char[std::strlen(other.m_name) + 1]; + std::strcpy(m_name, other.m_name); + } + } + + AMREX_GPU_HOST + Named& operator=(Named const & other) + { + if (&other != this) { + if (other.has_name()) { + m_name = new char[std::strlen(other.m_name) + 1]; + std::strcpy(m_name, other.m_name); + } + } + return *this; + } + + AMREX_GPU_HOST_DEVICE + Named ([[maybe_unused]] Named && other) noexcept + { + AMREX_IF_ON_HOST(( + std::swap(this->m_name, other.m_name); + other.m_name = nullptr; + )) + } + + AMREX_GPU_HOST_DEVICE + Named& operator=([[maybe_unused]] Named && other) noexcept + { + AMREX_IF_ON_HOST(( + std::swap(this->m_name, other.m_name); + other.m_name = nullptr; + )) + return *this; + } + + /** Return the user provided name of the element + * + * @return user defined and not necessarily unique name + */ + AMREX_FORCE_INLINE + std::string name () const + { + if (!has_name()) { + throw std::runtime_error("Name not set on element!"); + } + return std::string(m_name); + } + + /** Return true if the user provided a name for this element + * + * @return true if the user provided a name + */ + AMREX_FORCE_INLINE + bool has_name () const + { + return m_name != nullptr; + } + + private: + // Implementation note: + // This is used as a mixin class in elements that are copied to GPU. GPU compilers copy + // a whole element by its sizeof(T). + // To save on this copy operation at kernel start, this is implemented + // as a simple C pointer (8 bytes), contrary to a std::string (32 bytes) or + // a std::optional (40 bytes). m_name points to host-side memory and + // must not be dereferenced (used) on GPU. + char * m_name = nullptr; //! a user defined and not necessarily unique name of the element + }; + +} // namespace impactx::elements + +#endif // IMPACTX_ELEMENTS_MIXIN_NAMED_H diff --git a/src/particles/integrators/Integrators.H b/src/particles/integrators/Integrators.H index 636f04384..1bd770baf 100644 --- a/src/particles/integrators/Integrators.H +++ b/src/particles/integrators/Integrators.H @@ -137,7 +137,7 @@ namespace impactx::integrators // initialize numerical integration parameters amrex::ParticleReal const dz = (zout-zin)/nsteps; - amrex::ParticleReal const alpha = 1.0_prt - pow(2.0_prt,1.0/3.0); + amrex::ParticleReal const alpha = 1.0_prt - std::pow(2.0_prt,1.0/3.0); amrex::ParticleReal const tau2 = dz/(1.0_prt + alpha); amrex::ParticleReal const tau1 = tau2/2.0_prt; amrex::ParticleReal const tau3 = alpha*tau1; diff --git a/src/particles/spacecharge/PoissonSolve.cpp b/src/particles/spacecharge/PoissonSolve.cpp index 9d928424d..d9c9b3dd2 100644 --- a/src/particles/spacecharge/PoissonSolve.cpp +++ b/src/particles/spacecharge/PoissonSolve.cpp @@ -71,22 +71,6 @@ namespace impactx::spacecharge pp_algo.queryAdd("mlmg_max_iters", mlmg_max_iters); pp_algo.queryAdd("mlmg_verbosity", mlmg_verbosity); - struct PoissonBoundaryHandler { - amrex::Array const lobc = { - amrex::LinOpBCType::Dirichlet, - amrex::LinOpBCType::Dirichlet, - amrex::LinOpBCType::Dirichlet - }; - amrex::Array const hibc = { - amrex::LinOpBCType::Dirichlet, - amrex::LinOpBCType::Dirichlet, - amrex::LinOpBCType::Dirichlet - }; - //bool bcs_set = false; - //std::array dirichlet_flag; - //bool has_non_periodic = false; - } poisson_boundary_handler; - // create a vector to our fields, sorted by level amrex::Vector sorted_rho; amrex::Vector sorted_phi; @@ -97,6 +81,7 @@ namespace impactx::spacecharge } const bool do_single_precision_comms = false; + const bool eb_enabled = false; ablastr::fields::computePhi( sorted_rho, sorted_phi, @@ -109,12 +94,13 @@ namespace impactx::spacecharge pc.GetParGDB()->DistributionMap(), pc.GetParGDB()->boxArray(), ablastr::utils::enums::GridType::Collocated, - poisson_boundary_handler, is_solver_igf_on_lev0, + eb_enabled, do_single_precision_comms, rel_ref_ratio /* post_phi_calculation, + poisson_boundary_handler gett_new(0), eb_farray_box_factory */ diff --git a/src/particles/transformation/CoordinateTransformation.cpp b/src/particles/transformation/CoordinateTransformation.cpp index cbf105533..299c95972 100644 --- a/src/particles/transformation/CoordinateTransformation.cpp +++ b/src/particles/transformation/CoordinateTransformation.cpp @@ -62,7 +62,7 @@ namespace impactx::transformation amrex::ParticleReal *const AMREX_RESTRICT part_pz = soa_real[RealSoA::pz].dataPtr(); // Design value of pz/mc = beta*gamma - amrex::ParticleReal const pzd = sqrt(pow(pd, 2) - 1.0); + amrex::ParticleReal const pzd = std::sqrt(std::pow(pd, 2) - 1.0); ToFixedS const to_s(pzd); amrex::ParallelFor(np, [=] AMREX_GPU_DEVICE(long i) { diff --git a/src/particles/transformation/ToFixedS.H b/src/particles/transformation/ToFixedS.H index 10cb1b297..e18d4b136 100644 --- a/src/particles/transformation/ToFixedS.H +++ b/src/particles/transformation/ToFixedS.H @@ -59,9 +59,9 @@ namespace impactx::transformation using namespace amrex::literals; // compute value of reference ptd = -gamma - amrex::ParticleReal const argd = 1.0_prt + pow(m_pzd, 2); + amrex::ParticleReal const argd = 1.0_prt + std::pow(m_pzd, 2); AMREX_ASSERT_WITH_MESSAGE(argd > 0.0_prt, "invalid ptd arg (<=0)"); - amrex::ParticleReal const ptdf = argd > 0.0_prt ? -sqrt(argd) : -1.0_prt; + amrex::ParticleReal const ptdf = argd > 0.0_prt ? -std::sqrt(argd) : -1.0_prt; // transform momenta to dynamic units (e.g., so that momenta are // normalized by mc): @@ -70,9 +70,9 @@ namespace impactx::transformation pz = pz * m_pzd; // compute value of particle pt = -gamma - amrex::ParticleReal const arg = 1.0_prt + pow(m_pzd + pz, 2) + pow(px, 2) + pow(py, 2); + amrex::ParticleReal const arg = 1.0_prt + std::pow(m_pzd + pz, 2) + std::pow(px, 2) + std::pow(py, 2); AMREX_ASSERT_WITH_MESSAGE(arg > 0.0_prt, "invalid pt arg (<=0)"); - amrex::ParticleReal const ptf = arg > 0.0_prt ? -sqrt(arg) : -1.0_prt; + amrex::ParticleReal const ptf = arg > 0.0_prt ? -std::sqrt(arg) : -1.0_prt; // transform position and momentum (from fixed t to fixed s) x = x - px * z / (m_pzd + pz); diff --git a/src/particles/transformation/ToFixedT.H b/src/particles/transformation/ToFixedT.H index 577436ea3..af90cc132 100644 --- a/src/particles/transformation/ToFixedT.H +++ b/src/particles/transformation/ToFixedT.H @@ -62,9 +62,9 @@ namespace impactx::transformation constexpr amrex::ParticleReal tol = 1.0e-8_prt; // compute value of reference pzd = beta*gamma - amrex::ParticleReal const argd = -1.0_prt + pow(m_ptd, 2); + amrex::ParticleReal const argd = -1.0_prt + std::pow(m_ptd, 2); // AMREX_ASSERT_WITH_MESSAGE(argd > 0.0_prt, "invalid pzd arg (<=0)"); - amrex::ParticleReal const pzdf = argd > 0.0_prt ? sqrt(argd) : tol; + amrex::ParticleReal const pzdf = argd > 0.0_prt ? std::sqrt(argd) : tol; // transform momenta to dynamic units (eg, so that momenta are // normalized by mc): @@ -73,9 +73,9 @@ namespace impactx::transformation pt = pt * pzdf; // compute value of particle pz = beta*gamma - amrex::ParticleReal const arg = -1.0_prt + pow(m_ptd+pt, 2) - pow(px, 2) - pow(py, 2); + amrex::ParticleReal const arg = -1.0_prt + std::pow(m_ptd+pt, 2) - std::pow(px, 2) - std::pow(py, 2); // AMREX_ASSERT_WITH_MESSAGE(arg > 0.0_prt, "invalid pz arg (<=0)"); - amrex::ParticleReal const pzf = arg > 0.0_prt ? sqrt(arg) : tol; + amrex::ParticleReal const pzf = arg > 0.0_prt ? std::sqrt(arg) : tol; // transform position and momentum (from fixed s to fixed t) x = x + px*t/(m_ptd+pt); diff --git a/src/python/ImpactX.cpp b/src/python/ImpactX.cpp index 0326ca496..b67ab141b 100644 --- a/src/python/ImpactX.cpp +++ b/src/python/ImpactX.cpp @@ -62,20 +62,6 @@ void init_ImpactX (py::module& m) .def("load_inputs_file", [](ImpactX const & /* ix */, std::string const & filename) { -#if defined(AMREX_DEBUG) || defined(DEBUG) - // note: only in debug, since this is costly for the file - // system for highly parallel simulations with MPI - // possible improvement: - // - rank 0 tests file & broadcasts existence/failure - bool inputs_file_exists = false; - if (FILE *fp = fopen(filename.c_str(), "r")) { - fclose(fp); - inputs_file_exists = true; - } - AMREX_ALWAYS_ASSERT_WITH_MESSAGE(inputs_file_exists, - "load_inputs_file: file does not exist: " + filename); -#endif - amrex::ParmParse::addfile(filename); }) @@ -223,6 +209,16 @@ void init_ImpactX (py::module& m) }, "Number of longitudinal bins used for CSR calculations (default: 150)." ) + .def_property("eigenemittances", + [](ImpactX & /* ix */) { + return detail::get_or_throw("diag", "eigenemittances"); + }, + [](ImpactX & /* ix */, bool const enable) { + amrex::ParmParse pp_diag("diag"); + pp_diag.add("eigenemittances", enable); + }, + "Enable or disable eigenemittance diagnostic calculations (default: disabled)." + ) .def_property("space_charge", [](ImpactX & /* ix */) { return detail::get_or_throw("algo", "space_charge"); diff --git a/src/python/elements.cpp b/src/python/elements.cpp index 82f6ec6ee..b6403e71b 100644 --- a/src/python/elements.cpp +++ b/src/python/elements.cpp @@ -9,6 +9,8 @@ #include #include +#include +#include #include #include @@ -55,13 +57,80 @@ namespace using Element = typename T_PyClass::type; // py::class cl.def("push", - [](Element & el, ImpactXParticleContainer & pc, int step) { - el(pc, step); + [](Element & el, ImpactXParticleContainer & pc, int step, int period) { + el(pc, step, period); }, - py::arg("pc"), py::arg("step")=0, + py::arg("pc"), py::arg("step")=0, py::arg("period")=0, "Push first the reference particle, then all other particles." ); } + + /** Helper to format {key, value} pairs + * + * Expected outcome is ", key=value" with key as a string and appropriate formatting for value. + * + * @tparam T value type + * @param arg a key-value pair + * @return a string of the form ", key=value" + */ + template + std::string + format_extra (std::pair const & arg) + { + if constexpr (std::is_floating_point_v) + { + // float + // TODO: format as scientific number + return std::string(", ") + .append(arg.first) + .append("=") + .append(std::to_string(arg.second)); + } + else if constexpr (std::is_integral_v) + { + // int + return std::string(", ") + .append(arg.first) + .append("=") + .append(std::to_string(arg.second)); + } else + { + // already a string + return std::string(", ") + .append(arg.first) + .append("=") + .append(arg.second); + } + } + + /** Helper to build a __repr__ for an Element + * + * @tparam T_Element type for the C++ element type + * @tparam ExtraArgs type for pairs of name, value to add + * @param el the current element + * @param args pars of name, value to add + * @return a string suitable for Python's __repr__ + */ + template + std::string element_name (T_Element const & el, std::pair const &... args) + { + // Fixed element type name, e.g., "SBend" + std::string const type = T_Element::type; + + // User-provided element name, e.g., "name=bend1" + std::string const name = el.has_name() ? ", name=" + el.name() : ""; + + // Noteworthy element parameters, e.g., "ds=2.3, key=value, ..." + std::string extra_args; + ((extra_args.append(format_extra(args))), ...); + + // combine it all together + return ""; + } } void init_elements(py::module& m) @@ -73,6 +142,15 @@ void init_elements(py::module& m) // mixin classes + py::class_(me, "Named") + .def_property("name", + [](elements::Named & nm) { return nm.name(); }, + [](elements::Named & nm, std::string new_name) { nm.set_name(new_name); }, + "segment length in m" + ) + .def_property_readonly("has_name", &elements::Named::has_name) + ; + py::class_(me, "Thick") .def(py::init< amrex::ParticleReal, @@ -136,10 +214,11 @@ void init_elements(py::module& m) py::class_ py_BeamMonitor(me, "BeamMonitor"); py_BeamMonitor - .def(py::init(), + .def(py::init(), py::arg("name"), py::arg("backend") = "default", py::arg("encoding") = "g", + py::arg("period_sample_intervals") = 1, "This element writes the particle beam out to openPMD data." ) .def_property_readonly("name", @@ -194,11 +273,14 @@ void init_elements(py::module& m) // beam optics - py::class_ py_Aperture(me, "Aperture"); + py::class_ py_Aperture(me, "Aperture"); py_Aperture .def("__repr__", - [](Aperture const & /* ap */) { - return std::string(""); + [](Aperture const & ap) { + return element_name( + ap, + std::make_pair("shape", ap.shape_name(ap.m_shape)) + ); } ) .def(py::init([]( @@ -207,7 +289,8 @@ void init_elements(py::module& m) std::string const & shape, amrex::ParticleReal dx, amrex::ParticleReal dy, - amrex::ParticleReal rotation_degree + amrex::ParticleReal rotation_degree, + std::optional name ) { if (shape != "rectangular" && shape != "elliptical") @@ -216,7 +299,7 @@ void init_elements(py::module& m) Aperture::Shape const s = shape == "rectangular" ? Aperture::Shape::rectangular : Aperture::Shape::elliptical; - return new Aperture(xmax, ymax, s, dx, dy, rotation_degree); + return new Aperture(xmax, ymax, s, dx, dy, rotation_degree, name); }), py::arg("xmax"), py::arg("ymax"), @@ -224,20 +307,13 @@ void init_elements(py::module& m) py::arg("dx") = 0, py::arg("dy") = 0, py::arg("rotation") = 0, + py::arg("name") = py::none(), "A short collimator element applying a transverse aperture boundary." ) .def_property("shape", [](Aperture & ap) { - switch (ap.m_shape) - { - case Aperture::Shape::rectangular : // default - return "rectangular"; - case Aperture::Shape::elliptical : - return "elliptical"; - default: - throw std::runtime_error("Unknown shape"); - } + return ap.shape_name(ap.m_shape); }, [](Aperture & ap, std::string const & shape) { @@ -263,14 +339,14 @@ void init_elements(py::module& m) ; register_beamoptics_push(py_Aperture); - py::class_ py_ChrDrift(me, "ChrDrift"); + py::class_ py_ChrDrift(me, "ChrDrift"); py_ChrDrift .def("__repr__", [](ChrDrift const & chr_drift) { - std::string r = ""); - return r; + return element_name( + chr_drift, + std::make_pair("ds", chr_drift.ds()) + ); } ) .def(py::init< @@ -278,28 +354,29 @@ void init_elements(py::module& m) amrex::ParticleReal, amrex::ParticleReal, amrex::ParticleReal, - int + int, + std::optional >(), py::arg("ds"), py::arg("dx") = 0, py::arg("dy") = 0, py::arg("rotation") = 0, py::arg("nslice") = 1, + py::arg("name") = py::none(), "A Drift with chromatic effects included." ) ; register_beamoptics_push(py_ChrDrift); - py::class_ py_ChrQuad(me, "ChrQuad"); + py::class_ py_ChrQuad(me, "ChrQuad"); py_ChrQuad .def("__repr__", [](ChrQuad const & chr_quad) { - std::string r = ""); - return r; + return element_name( + chr_quad, + std::make_pair("ds", chr_quad.ds()), + std::make_pair("k", chr_quad.m_k) + ); } ) .def(py::init< @@ -309,7 +386,8 @@ void init_elements(py::module& m) amrex::ParticleReal, amrex::ParticleReal, amrex::ParticleReal, - int + int, + std::optional >(), py::arg("ds"), py::arg("k"), @@ -318,6 +396,7 @@ void init_elements(py::module& m) py::arg("dy") = 0, py::arg("rotation") = 0, py::arg("nslice") = 1, + py::arg("name") = py::none(), "A Quadrupole magnet with chromatic effects included." ) .def_property("k", @@ -333,16 +412,15 @@ void init_elements(py::module& m) ; register_beamoptics_push(py_ChrQuad); - py::class_ py_ChrPlasmaLens(me, "ChrPlasmaLens"); + py::class_ py_ChrPlasmaLens(me, "ChrPlasmaLens"); py_ChrPlasmaLens .def("__repr__", [](ChrPlasmaLens const & chr_pl_lens) { - std::string r = ""); - return r; + return element_name( + chr_pl_lens, + std::make_pair("ds", chr_pl_lens.ds()), + std::make_pair("k", chr_pl_lens.m_k) + ); } ) .def(py::init< @@ -352,7 +430,8 @@ void init_elements(py::module& m) amrex::ParticleReal, amrex::ParticleReal, amrex::ParticleReal, - int + int, + std::optional >(), py::arg("ds"), py::arg("k"), @@ -361,6 +440,7 @@ void init_elements(py::module& m) py::arg("dy") = 0, py::arg("rotation") = 0, py::arg("nslice") = 1, + py::arg("name") = py::none(), "An active Plasma Lens with chromatic effects included." ) .def_property("k", @@ -376,18 +456,16 @@ void init_elements(py::module& m) ; register_beamoptics_push(py_ChrPlasmaLens); - py::class_ py_ChrAcc(me, "ChrAcc"); + py::class_ py_ChrAcc(me, "ChrAcc"); py_ChrAcc .def("__repr__", [](ChrAcc const & chr_acc) { - std::string r = ""); - return r; + return element_name( + chr_acc, + std::make_pair("ds", chr_acc.ds()), + std::make_pair("ez", chr_acc.m_ez), + std::make_pair("bz", chr_acc.m_bz) + ); } ) .def(py::init< @@ -397,7 +475,8 @@ void init_elements(py::module& m) amrex::ParticleReal, amrex::ParticleReal, amrex::ParticleReal, - int + int, + std::optional >(), py::arg("ds"), py::arg("ez"), @@ -406,6 +485,7 @@ void init_elements(py::module& m) py::arg("dy") = 0, py::arg("rotation") = 0, py::arg("nslice") = 1, + py::arg("name") = py::none(), "A region of Uniform Acceleration, with chromatic effects included." ) .def_property("ez", @@ -421,20 +501,17 @@ void init_elements(py::module& m) ; register_beamoptics_push(py_ChrAcc); - py::class_ py_ConstF(me, "ConstF"); + py::class_ py_ConstF(me, "ConstF"); py_ConstF .def("__repr__", [](ConstF const & constf) { - std::string r = ""); - return r; + return element_name( + constf, + std::make_pair("ds", constf.ds()), + std::make_pair("kx", constf.m_kx), + std::make_pair("ky", constf.m_ky), + std::make_pair("kt", constf.m_kt) + ); } ) .def(py::init< @@ -445,7 +522,8 @@ void init_elements(py::module& m) amrex::ParticleReal, amrex::ParticleReal, amrex::ParticleReal, - int + int, + std::optional >(), py::arg("ds"), py::arg("kx"), @@ -455,6 +533,7 @@ void init_elements(py::module& m) py::arg("dy") = 0, py::arg("rotation") = 0, py::arg("nslice") = 1, + py::arg("name") = py::none(), "A linear Constant Focusing element." ) .def_property("kx", @@ -475,20 +554,17 @@ void init_elements(py::module& m) ; register_beamoptics_push(py_ConstF); - py::class_ py_DipEdge(me, "DipEdge"); + py::class_ py_DipEdge(me, "DipEdge"); py_DipEdge .def("__repr__", [](DipEdge const & dip_edge) { - std::string r = ""); - return r; + return element_name( + dip_edge, + std::make_pair("psi", dip_edge.m_psi), + std::make_pair("rc", dip_edge.m_rc), + std::make_pair("g", dip_edge.m_g), + std::make_pair("K2", dip_edge.m_K2) + ); } ) .def(py::init< @@ -498,7 +574,8 @@ void init_elements(py::module& m) amrex::ParticleReal, amrex::ParticleReal, amrex::ParticleReal, - amrex::ParticleReal + amrex::ParticleReal, + std::optional >(), py::arg("psi"), py::arg("rc"), @@ -507,6 +584,7 @@ void init_elements(py::module& m) py::arg("dx") = 0, py::arg("dy") = 0, py::arg("rotation") = 0, + py::arg("name") = py::none(), "Edge focusing associated with bend entry or exit." ) .def_property("psi", @@ -532,14 +610,14 @@ void init_elements(py::module& m) ; register_beamoptics_push(py_DipEdge); - py::class_ py_Drift(me, "Drift"); + py::class_ py_Drift(me, "Drift"); py_Drift .def("__repr__", [](Drift const & drift) { - std::string r = ""); - return r; + return element_name( + drift, + std::make_pair("ds", drift.ds()) + ); } ) .def(py::init< @@ -547,26 +625,28 @@ void init_elements(py::module& m) amrex::ParticleReal, amrex::ParticleReal, amrex::ParticleReal, - int + int, + std::optional >(), py::arg("ds"), py::arg("dx") = 0, py::arg("dy") = 0, py::arg("rotation") = 0, py::arg("nslice") = 1, + py::arg("name") = py::none(), "A drift." ) ; register_beamoptics_push(py_Drift); - py::class_ py_ExactDrift(me, "ExactDrift"); + py::class_ py_ExactDrift(me, "ExactDrift"); py_ExactDrift .def("__repr__", [](ExactDrift const & exact_drift) { - std::string r = ""); - return r; + return element_name( + exact_drift, + std::make_pair("ds", exact_drift.ds()) + ); } ) .def(py::init< @@ -574,30 +654,30 @@ void init_elements(py::module& m) amrex::ParticleReal, amrex::ParticleReal, amrex::ParticleReal, - int + int, + std::optional >(), py::arg("ds"), py::arg("dx") = 0, py::arg("dy") = 0, py::arg("rotation") = 0, py::arg("nslice") = 1, + py::arg("name") = py::none(), "A Drift using the exact nonlinear map." ) ; register_beamoptics_push(py_ExactDrift); - py::class_ py_ExactSbend(me, "ExactSbend"); + py::class_ py_ExactSbend(me, "ExactSbend"); py_ExactSbend .def("__repr__", [](ExactSbend const & exact_sbend) { - std::string r = ""); - return r; + return element_name( + exact_sbend, + std::make_pair("ds", exact_sbend.ds()), + std::make_pair("phi", exact_sbend.m_phi), + std::make_pair("B", exact_sbend.m_B) + ); } ) .def(py::init< @@ -607,7 +687,8 @@ void init_elements(py::module& m) amrex::ParticleReal, amrex::ParticleReal, amrex::ParticleReal, - int + int, + std::optional >(), py::arg("ds"), py::arg("phi"), @@ -616,6 +697,7 @@ void init_elements(py::module& m) py::arg("dy") = 0, py::arg("rotation") = 0, py::arg("nslice") = 1, + py::arg("name") = py::none(), "An ideal sector bend using the exact nonlinear map. When B = 0, the reference bending radius is defined by r0 = length / (angle in rad), corresponding to a magnetic field of B = rigidity / r0; otherwise the reference bending radius is defined by r0 = rigidity / B." ) .def_property("phi", @@ -631,16 +713,15 @@ void init_elements(py::module& m) ; register_beamoptics_push(py_ExactSbend); - py::class_ py_Kicker(me, "Kicker"); + py::class_ py_Kicker(me, "Kicker"); py_Kicker .def("__repr__", [](Kicker const & kicker) { - std::string r = ""); - return r; + return element_name( + kicker, + std::make_pair("xkick", kicker.m_xkick), + std::make_pair("ykick", kicker.m_ykick) + ); } ) .def(py::init([]( @@ -649,7 +730,8 @@ void init_elements(py::module& m) std::string const & unit, amrex::ParticleReal dx, amrex::ParticleReal dy, - amrex::ParticleReal rotation_degree + amrex::ParticleReal rotation_degree, + std::optional name ) { if (unit != "dimensionless" && unit != "T-m") @@ -658,14 +740,15 @@ void init_elements(py::module& m) Kicker::UnitSystem const u = unit == "dimensionless" ? Kicker::UnitSystem::dimensionless : Kicker::UnitSystem::Tm; - return new Kicker(xkick, ykick, u, dx, dy, rotation_degree); + return new Kicker(xkick, ykick, u, dx, dy, rotation_degree, name); }), - py::arg("xkick"), + py::arg("xkick"), py::arg("ykick"), py::arg("unit") = "dimensionless", py::arg("dx") = 0, py::arg("dy") = 0, py::arg("rotation") = 0, + py::arg("name") = py::none(), R"(A thin transverse kicker element. Kicks are for unit "dimensionless" or in "T-m".)" ) .def_property("xkick", @@ -682,18 +765,17 @@ void init_elements(py::module& m) ; register_beamoptics_push(py_Kicker); - py::class_ py_Multipole(me, "Multipole"); + py::class_ py_Multipole(me, "Multipole"); py_Multipole .def("__repr__", [](Multipole const & multipole) { - std::string r = ""); - return r; + return element_name( + multipole, + std::make_pair("multipole", multipole.m_multipole), + std::make_pair("mfactorial", multipole.m_mfactorial), + std::make_pair("K_normal", multipole.m_Kn), + std::make_pair("K_skew", multipole.m_Ks) + ); } ) .def(py::init< @@ -702,7 +784,8 @@ void init_elements(py::module& m) amrex::ParticleReal, amrex::ParticleReal, amrex::ParticleReal, - amrex::ParticleReal + amrex::ParticleReal, + std::optional >(), py::arg("multipole"), py::arg("K_normal"), @@ -710,6 +793,7 @@ void init_elements(py::module& m) py::arg("dx") = 0, py::arg("dy") = 0, py::arg("rotation") = 0, + py::arg("name") = py::none(), "A general thin multipole element." ) .def_property("multipole", @@ -730,10 +814,10 @@ void init_elements(py::module& m) ; register_beamoptics_push(py_Multipole); - py::class_ py_None(me, "Empty"); - py_None + py::class_ py_Empty(me, "Empty"); + py_Empty .def("__repr__", - [](Empty const & /* none */) { + [](Empty const & /* empty */) { return std::string(""); } ) @@ -741,18 +825,30 @@ void init_elements(py::module& m) "This element does nothing." ) ; - register_beamoptics_push(py_None); + register_beamoptics_push(py_Empty); - py::class_ py_NonlinearLens(me, "NonlinearLens"); + py::class_ py_Marker(me, "Marker"); + py_Marker + .def("__repr__", + [](Marker const & marker) { + return element_name(marker); + } + ) + .def(py::init(), + "This named element does nothing." + ) + ; + register_beamoptics_push(py_Marker); + + py::class_ py_NonlinearLens(me, "NonlinearLens"); py_NonlinearLens .def("__repr__", [](NonlinearLens const & nl) { - std::string r = ""); - return r; + return element_name( + nl, + std::make_pair("knll", nl.m_knll), + std::make_pair("cnll", nl.m_cnll) + ); } ) .def(py::init< @@ -760,13 +856,15 @@ void init_elements(py::module& m) amrex::ParticleReal, amrex::ParticleReal, amrex::ParticleReal, - amrex::ParticleReal + amrex::ParticleReal, + std::optional >(), py::arg("knll"), py::arg("cnll"), py::arg("dx") = 0, py::arg("dy") = 0, py::arg("rotation") = 0, + py::arg("name") = py::none(), "Single short segment of the nonlinear magnetic insert element." ) .def_property("knll", @@ -782,20 +880,23 @@ void init_elements(py::module& m) ; register_beamoptics_push(py_NonlinearLens); - py::class_(me, "Programmable", py::dynamic_attr()) + py::class_(me, "Programmable", py::dynamic_attr()) .def("__repr__", [](Programmable const & prg) { - std::string r = ""); - return r; + return element_name( + prg, + std::make_pair("ds", prg.ds()) + ); } ) .def(py::init< amrex::ParticleReal, - int + int, + std::optional >(), - py::arg("ds") = 0.0, py::arg("nslice") = 1, + py::arg("ds") = 0.0, + py::arg("nslice") = 1, + py::arg("name") = py::none(), "A programmable beam optics element." ) .def_property("nslice", @@ -814,9 +915,9 @@ void init_elements(py::module& m) .def_property("push", [](Programmable & p) { return p.m_push; }, [](Programmable & p, - std::function new_hook + std::function new_hook ) { p.m_push = std::move(new_hook); }, - "hook for push of whole container (pc, step)" + "hook for push of whole container (pc, step, period)" ) .def_property("beam_particles", [](Programmable & p) { return p.m_beam_particles; }, @@ -834,16 +935,15 @@ void init_elements(py::module& m) ) ; - py::class_ py_Quad(me, "Quad"); + py::class_ py_Quad(me, "Quad"); py_Quad .def("__repr__", [](Quad const & quad) { - std::string r = ""); - return r; + return element_name( + quad, + std::make_pair("ds", quad.ds()), + std::make_pair("k", quad.m_k) + ); } ) .def(py::init< @@ -852,7 +952,8 @@ void init_elements(py::module& m) amrex::ParticleReal, amrex::ParticleReal, amrex::ParticleReal, - int + int, + std::optional >(), py::arg("ds"), py::arg("k"), @@ -860,6 +961,7 @@ void init_elements(py::module& m) py::arg("dy") = 0, py::arg("rotation") = 0, py::arg("nslice") = 1, + py::arg("name") = py::none(), "A Quadrupole magnet." ) .def_property("k", @@ -870,20 +972,17 @@ void init_elements(py::module& m) ; register_beamoptics_push(py_Quad); - py::class_ py_RFCavity(me, "RFCavity"); + py::class_ py_RFCavity(me, "RFCavity"); py_RFCavity .def("__repr__", [](RFCavity const & rfc) { - std::string r = ""); - return r; + return element_name( + rfc, + std::make_pair("ds", rfc.ds()), + std::make_pair("escale", rfc.m_escale), + std::make_pair("freq", rfc.m_freq), + std::make_pair("phase", rfc.m_phase) + ); } ) .def(py::init< @@ -897,7 +996,8 @@ void init_elements(py::module& m) amrex::ParticleReal, amrex::ParticleReal, int, - int + int, + std::optional >(), py::arg("ds"), py::arg("escale"), @@ -910,6 +1010,7 @@ void init_elements(py::module& m) py::arg("rotation") = 0, py::arg("mapsteps") = 1, py::arg("nslice") = 1, + py::arg("name") = py::none(), "An RF cavity." ) .def_property("escale", @@ -937,16 +1038,15 @@ void init_elements(py::module& m) ; register_beamoptics_push(py_RFCavity); - py::class_ py_Sbend(me, "Sbend"); + py::class_ py_Sbend(me, "Sbend"); py_Sbend .def("__repr__", [](Sbend const & sbend) { - std::string r = ""); - return r; + return element_name( + sbend, + std::make_pair("ds", sbend.ds()), + std::make_pair("rc", sbend.m_rc) + ); } ) .def(py::init< @@ -955,7 +1055,8 @@ void init_elements(py::module& m) amrex::ParticleReal, amrex::ParticleReal, amrex::ParticleReal, - int + int, + std::optional >(), py::arg("ds"), py::arg("rc"), @@ -963,6 +1064,7 @@ void init_elements(py::module& m) py::arg("dy") = 0, py::arg("rotation") = 0, py::arg("nslice") = 1, + py::arg("name") = py::none(), "An ideal sector bend." ) .def_property("rc", @@ -973,18 +1075,16 @@ void init_elements(py::module& m) ; register_beamoptics_push(py_Sbend); - py::class_ py_CFbend(me, "CFbend"); + py::class_ py_CFbend(me, "CFbend"); py_CFbend .def("__repr__", [](CFbend const & cfbend) { - std::string r = ""); - return r; + return element_name( + cfbend, + std::make_pair("ds", cfbend.ds()), + std::make_pair("rc", cfbend.m_rc), + std::make_pair("k", cfbend.m_k) + ); } ) .def(py::init< @@ -994,7 +1094,8 @@ void init_elements(py::module& m) amrex::ParticleReal, amrex::ParticleReal, amrex::ParticleReal, - int + int, + std::optional >(), py::arg("ds"), py::arg("rc"), @@ -1003,6 +1104,7 @@ void init_elements(py::module& m) py::arg("dy") = 0, py::arg("rotation") = 0, py::arg("nslice") = 1, + py::arg("name") = py::none(), "An ideal combined function bend (sector bend with quadrupole component)." ) .def_property("rc", @@ -1018,16 +1120,15 @@ void init_elements(py::module& m) ; register_beamoptics_push(py_CFbend); - py::class_ py_Buncher(me, "Buncher"); + py::class_ py_Buncher(me, "Buncher"); py_Buncher .def("__repr__", [](Buncher const & buncher) { - std::string r = ""); - return r; + return element_name( + buncher, + std::make_pair("V", buncher.m_V), + std::make_pair("k", buncher.m_k) + ); } ) .def(py::init< @@ -1035,13 +1136,15 @@ void init_elements(py::module& m) amrex::ParticleReal, amrex::ParticleReal, amrex::ParticleReal, - amrex::ParticleReal + amrex::ParticleReal, + std::optional >(), py::arg("V"), py::arg("k"), py::arg("dx") = 0, py::arg("dy") = 0, py::arg("rotation") = 0, + py::arg("name") = py::none(), "A short linear RF cavity element at zero-crossing for bunching." ) .def_property("V", @@ -1057,18 +1160,16 @@ void init_elements(py::module& m) ; register_beamoptics_push(py_Buncher); - py::class_ py_ShortRF(me, "ShortRF"); + py::class_ py_ShortRF(me, "ShortRF"); py_ShortRF .def("__repr__", [](ShortRF const & short_rf) { - std::string r = ""); - return r; + return element_name( + short_rf, + std::make_pair("V", short_rf.m_V), + std::make_pair("freq", short_rf.m_freq), + std::make_pair("phase", short_rf.m_phase) + ); } ) .def(py::init< @@ -1077,7 +1178,8 @@ void init_elements(py::module& m) amrex::ParticleReal, amrex::ParticleReal, amrex::ParticleReal, - amrex::ParticleReal + amrex::ParticleReal, + std::optional >(), py::arg("V"), py::arg("freq"), @@ -1085,6 +1187,7 @@ void init_elements(py::module& m) py::arg("dx") = 0, py::arg("dy") = 0, py::arg("rotation") = 0, + py::arg("name") = py::none(), "A short RF cavity element." ) .def_property("V", @@ -1105,16 +1208,15 @@ void init_elements(py::module& m) ; register_beamoptics_push(py_ShortRF); - py::class_ py_SoftSolenoid(me, "SoftSolenoid"); + py::class_ py_SoftSolenoid(me, "SoftSolenoid"); py_SoftSolenoid .def("__repr__", [](SoftSolenoid const & soft_sol) { - std::string r = ""); - return r; + return element_name( + soft_sol, + std::make_pair("ds", soft_sol.ds()), + std::make_pair("bscale", soft_sol.m_bscale) + ); } ) .def(py::init< @@ -1127,7 +1229,8 @@ void init_elements(py::module& m) amrex::ParticleReal, amrex::ParticleReal, int, - int + int, + std::optional >(), py::arg("ds"), py::arg("bscale"), @@ -1139,6 +1242,7 @@ void init_elements(py::module& m) py::arg("rotation") = 0, py::arg("mapsteps") = 1, py::arg("nslice") = 1, + py::arg("name") = py::none(), "A soft-edge solenoid." ) .def_property("bscale", @@ -1161,16 +1265,15 @@ void init_elements(py::module& m) ; register_beamoptics_push(py_SoftSolenoid); - py::class_ py_Sol(me, "Sol"); + py::class_ py_Sol(me, "Sol"); py_Sol .def("__repr__", - [](Sol const & soft_sol) { - std::string r = ""); - return r; + [](Sol const & sol) { + return element_name( + sol, + std::make_pair("ds", sol.ds()), + std::make_pair("ks", sol.m_ks) + ); } ) .def(py::init< @@ -1179,7 +1282,8 @@ void init_elements(py::module& m) amrex::ParticleReal, amrex::ParticleReal, amrex::ParticleReal, - int + int, + std::optional >(), py::arg("ds"), py::arg("ks"), @@ -1187,6 +1291,7 @@ void init_elements(py::module& m) py::arg("dy") = 0, py::arg("rotation") = 0, py::arg("nslice") = 1, + py::arg("name") = py::none(), "An ideal hard-edge Solenoid magnet." ) .def_property("ks", @@ -1197,24 +1302,25 @@ void init_elements(py::module& m) ; register_beamoptics_push(py_Sol); - py::class_ py_PRot(me, "PRot"); + py::class_ py_PRot(me, "PRot"); py_PRot .def("__repr__", [](PRot const & prot) { - std::string r = ""); - return r; + return element_name( + prot, + std::make_pair("phi_in", prot.m_phi_in), + std::make_pair("phi_out", prot.m_phi_out) + ); } ) .def(py::init< amrex::ParticleReal, - amrex::ParticleReal + amrex::ParticleReal, + std::optional >(), py::arg("phi_in"), py::arg("phi_out"), + py::arg("name") = py::none(), "An exact pole-face rotation in the x-z plane. Both angles are in degrees." ) .def_property("phi_in", @@ -1230,16 +1336,15 @@ void init_elements(py::module& m) ; register_beamoptics_push(py_PRot); - py::class_ py_SoftQuadrupole(me, "SoftQuadrupole"); + py::class_ py_SoftQuadrupole(me, "SoftQuadrupole"); py_SoftQuadrupole .def("__repr__", [](SoftQuadrupole const & soft_quad) { - std::string r = ""); - return r; + return element_name( + soft_quad, + std::make_pair("ds", soft_quad.ds()), + std::make_pair("gscale", soft_quad.m_gscale) + ); } ) .def(py::init< @@ -1251,7 +1356,8 @@ void init_elements(py::module& m) amrex::ParticleReal, amrex::ParticleReal, int, - int + int, + std::optional >(), py::arg("ds"), py::arg("gscale"), @@ -1262,6 +1368,7 @@ void init_elements(py::module& m) py::arg("rotation") = 0, py::arg("mapsteps") = 1, py::arg("nslice") = 1, + py::arg("name") = py::none(), "A soft-edge quadrupole." ) .def_property("gscale", @@ -1279,16 +1386,15 @@ void init_elements(py::module& m) ; register_beamoptics_push(py_SoftQuadrupole); - py::class_ py_ThinDipole(me, "ThinDipole"); + py::class_ py_ThinDipole(me, "ThinDipole"); py_ThinDipole .def("__repr__", [](ThinDipole const & thin_dp) { - std::string r = ""); - return r; + return element_name( + thin_dp, + std::make_pair("theta", thin_dp.m_theta), + std::make_pair("rc", thin_dp.m_rc) + ); } ) .def(py::init< @@ -1296,13 +1402,15 @@ void init_elements(py::module& m) amrex::ParticleReal, amrex::ParticleReal, amrex::ParticleReal, - amrex::ParticleReal + amrex::ParticleReal, + std::optional >(), py::arg("theta"), py::arg("rc"), py::arg("dx") = 0, py::arg("dy") = 0, py::arg("rotation") = 0, + py::arg("name") = py::none(), "A thin kick model of a dipole bend." ) .def_property("theta", @@ -1318,17 +1426,15 @@ void init_elements(py::module& m) ; register_beamoptics_push(py_ThinDipole); - py::class_ py_TaperedPL(me, "TaperedPL"); + py::class_ py_TaperedPL(me, "TaperedPL"); py_TaperedPL .def("__repr__", [](TaperedPL const & taperedpl) { - std::string r = ""); - return r; + return element_name( + taperedpl, + std::make_pair("k", taperedpl.m_k), + std::make_pair("taper", taperedpl.m_taper) + ); } ) .def(py::init< @@ -1337,7 +1443,8 @@ void init_elements(py::module& m) int, amrex::ParticleReal, amrex::ParticleReal, - amrex::ParticleReal + amrex::ParticleReal, + std::optional >(), py::arg("k"), py::arg("taper"), @@ -1345,6 +1452,7 @@ void init_elements(py::module& m) py::arg("dx") = 0, py::arg("dy") = 0, py::arg("rotation") = 0, + py::arg("name") = py::none(), R"doc(A thin nonlinear plasma lens with transverse (horizontal) taper .. math:: @@ -1373,9 +1481,9 @@ void init_elements(py::module& m) register_beamoptics_push(py_TaperedPL); - // free-standing push function + // freestanding push function m.def("push", &Push, - py::arg("pc"), py::arg("element"), py::arg("step")=0, + py::arg("pc"), py::arg("element"), py::arg("step")=0, py::arg("period")=0, "Push particles through an element" ); diff --git a/src/python/impactx/__init__.py b/src/python/impactx/__init__.py index 420de40ce..34cac419a 100644 --- a/src/python/impactx/__init__.py +++ b/src/python/impactx/__init__.py @@ -18,6 +18,7 @@ # import core bindings to C++ from . import impactx_pybind as cxx +from .distribution_input_helpers import twiss # noqa from .extensions.ImpactXParIter import register_ImpactXParIter_extension from .extensions.ImpactXParticleContainer import ( register_ImpactXParticleContainer_extension, diff --git a/src/python/impactx/__init__.pyi b/src/python/impactx/__init__.pyi index c3d925f89..f0b3b94d6 100644 --- a/src/python/impactx/__init__.pyi +++ b/src/python/impactx/__init__.pyi @@ -17,6 +17,7 @@ from __future__ import annotations import os as os from amrex import space3d as amr +from impactx.distribution_input_helpers import twiss from impactx.extensions.ImpactXParIter import register_ImpactXParIter_extension from impactx.extensions.ImpactXParticleContainer import ( register_ImpactXParticleContainer_extension, @@ -37,7 +38,13 @@ from impactx.impactx_pybind import ( ) from impactx.madx_to_impactx import read_beam, read_lattice -from . import MADXParser, extensions, impactx_pybind, madx_to_impactx +from . import ( + MADXParser, + distribution_input_helpers, + extensions, + impactx_pybind, + madx_to_impactx, +) __all__ = [ "Config", @@ -52,6 +59,7 @@ __all__ = [ "coordinate_transformation", "cxx", "distribution", + "distribution_input_helpers", "elements", "extensions", "impactx_pybind", @@ -64,13 +72,14 @@ __all__ = [ "register_ImpactXParticleContainer_extension", "s", "t", + "twiss", "wakeconvolution", ] __author__: str = ( "Axel Huebl, Chad Mitchell, Ryan Sandberg, Marco Garten, Ji Qiang, et al." ) __license__: str = "BSD-3-Clause-LBNL" -__version__: str = "24.08" +__version__: str = "24.09" s: impactx_pybind.CoordSystem # value = t: impactx_pybind.CoordSystem # value = cxx = impactx_pybind diff --git a/src/python/impactx/dashboard/Analyze/plotsMain.py b/src/python/impactx/dashboard/Analyze/plotsMain.py index 4d71ffb86..5a8a02bb4 100644 --- a/src/python/impactx/dashboard/Analyze/plotsMain.py +++ b/src/python/impactx/dashboard/Analyze/plotsMain.py @@ -7,17 +7,17 @@ """ import asyncio -import contextlib import glob import io import os from trame.widgets import matplotlib, plotly, vuetify +from wurlitzer import pipes +from ..simulation import run_simulation from ..trame_setup import setup_server from .analyzeFunctions import AnalyzeFunctions from .plot_ParameterEvolutionOverS.overS import line_plot_1d -from .plot_PhaseSpaceProjections.phaseSpace import run_simulation server, state, ctrl = setup_server() @@ -140,11 +140,11 @@ def update_plot(): def run_simulation_impactX(): buf = io.StringIO() - with contextlib.redirect_stdout(buf), contextlib.redirect_stderr(buf): + with pipes(stdout=buf, stderr=buf): state.simulation_data = run_simulation() buf.seek(0) - lines = [line.strip() for line in buf] + lines = [line.strip() for line in buf.getvalue().splitlines()] # Use $nextTick to ensure the terminal is fully rendered before printing async def print_lines(): diff --git a/src/python/impactx/dashboard/Input/generalFunctions.py b/src/python/impactx/dashboard/Input/generalFunctions.py index dcb4717f0..42e7f66cd 100644 --- a/src/python/impactx/dashboard/Input/generalFunctions.py +++ b/src/python/impactx/dashboard/Input/generalFunctions.py @@ -64,38 +64,53 @@ def determine_input_type(value): return value, str @staticmethod - def validate_against(input_value, value_type): + def validate_against(input_value, value_type, additional_conditions=None): """ - Returns an error message if the input value type does not match the desired type. + Validates the input value against the desired type and additional conditions. :param input_value: The value to validate. :param value_type: The desired type ('int', 'float', 'str'). + :param conditions: A list of additional conditions to validate. :return: A list of error messages. An empty list if there are no errors. """ + errors = [] + value = None + # value_type checking if value_type == "int": if input_value is None: - return ["Must be an integer"] - try: - int(input_value) - return [] - except ValueError: - return ["Must be an integer"] - + errors.append("Must be an integer") + else: + try: + value = int(input_value) + except ValueError: + errors.append("Must be an integer") elif value_type == "float": if input_value is None: - return ["Must be a float"] - try: - float(input_value) - return [] - except ValueError: - return ["Must be a float"] - + errors.append("Must be a float") + else: + try: + value = float(input_value) + except ValueError: + errors.append("Must be a float") elif value_type == "str": if input_value is None: - return ["Must be a string"] - return [] + errors.append("Must be a string") + else: + value = str(input_value) else: - return ["Unknown type"] + errors.append("Unknown type") + + # addition_conditions checking + if errors == [] and additional_conditions: + for condition in additional_conditions: + if condition == "non_zero" and value == 0: + errors.append("Must be non-zero") + if condition == "positive" and value <= 0: + errors.append("Must be positive") + if condition == "negative" and value >= 0: + errors.append("Must be negative") + + return errors @staticmethod def update_simulation_validation_status(): @@ -128,6 +143,11 @@ def update_simulation_validation_status(): error_details.append(f"Kinetic Energy: {state.kin_energy_validation}") if state.bunch_charge_C_validation: error_details.append(f"Bunch Charge: {state.bunch_charge_C_validation}") + if state.charge_qe_validation: + error_details.append(f"Ref. Particle Charge: {state.charge_qe_validation}") + if state.mass_MeV_validation: + error_details.append(f"Ref. Particle Mass: {state.mass_MeV}") + if state.selectedLatticeList == []: error_details.append("LatticeListIsEmpty") diff --git a/src/python/impactx/dashboard/Input/inputParameters/inputMain.py b/src/python/impactx/dashboard/Input/inputParameters/inputMain.py index 52e7e63ba..1f9699564 100644 --- a/src/python/impactx/dashboard/Input/inputParameters/inputMain.py +++ b/src/python/impactx/dashboard/Input/inputParameters/inputMain.py @@ -21,9 +21,11 @@ @ctrl.add("on_input_change") def validate_and_convert_to_correct_type( - value, desired_type, state_name, validation_name + value, desired_type, state_name, validation_name, conditions=None ): - validation_result = generalFunctions.validate_against(value, desired_type) + validation_result = generalFunctions.validate_against( + value, desired_type, conditions + ) setattr(state, validation_name, validation_result) generalFunctions.update_simulation_validation_status() @@ -70,17 +72,21 @@ def __init__(self): state.bunch_charge_C = 1.0e-9 state.kin_energy_unit = "MeV" state.old_kin_energy_unit = "MeV" + state.charge_qe = -1 + state.mass_MeV = 0.510998950 state.npart_validation = [] state.kin_energy_validation = [] state.bunch_charge_C_validation = [] + state.mass_MeV_validation = [] + state.charge_qe_validation = [] def card(self): """ Creates UI content for beam properties. """ - with vuetify.VCard(style="width: 340px; height: 300px"): + with vuetify.VCard(style="width: 340px; height: 350px"): with vuetify.VCardTitle("Input Parameters"): vuetify.VSpacer() vuetify.VIcon( @@ -96,6 +102,33 @@ def card(self): items=([1, 2, 3],), dense=True, ) + with vuetify.VRow(classes="my-2"): + with vuetify.VCol(cols=6, classes="py-0"): + vuetify.VTextField( + label="Ref. Particle Charge", + v_model=("charge_qe",), + suffix="qe", + type="number", + dense=True, + error_messages=("charge_qe_validation",), + change=( + ctrl.on_input_change, + "[$event, 'int','charge_qe','charge_qe_validation', ['non_zero']]", + ), + ) + with vuetify.VCol(cols=6, classes="py-0"): + vuetify.VTextField( + label="Ref. Particle Mass", + v_model=("mass_MeV",), + suffix="MeV", + type="number", + dense=True, + error_messages=("mass_MeV_validation",), + change=( + ctrl.on_input_change, + "[$event, 'float','mass_MeV','mass_MeV_validation', ['positive']]", + ), + ) with vuetify.VRow(classes="my-0"): with vuetify.VCol(cols=12, classes="py-0"): vuetify.VTextField( diff --git a/src/python/impactx/dashboard/Toolbar/exportTemplate.py b/src/python/impactx/dashboard/Toolbar/exportTemplate.py new file mode 100644 index 000000000..e58e6e02a --- /dev/null +++ b/src/python/impactx/dashboard/Toolbar/exportTemplate.py @@ -0,0 +1,91 @@ +from ..Input.distributionParameters.distributionMain import parameter_input_checker +from ..Input.latticeConfiguration.latticeMain import parameter_input_checker_for_lattice +from ..trame_setup import setup_server + +server, state, ctrl = setup_server() + +# ----------------------------------------------------------------------------- +# Helper Functions +# ----------------------------------------------------------------------------- + + +def build_distribution_list(): + """ + Generates an instance of distribution inputs + as a string for exporting purposes. + """ + distribution_name = state.selectedDistribution + parameters = parameter_input_checker() + + distribution_parameters = ",\n ".join( + f"{key}={value}" for key, value in parameters.items() + ) + + return ( + f"distr = distribution.{distribution_name}(\n {distribution_parameters},\n)" + ) + + +def build_lattice_list(): + """ + Generates a string representation of lattice element + inputs for export purposes. + """ + + lattice_elements = ",\n ".join( + f'elements.{element["name"]}(' + + ", ".join( + f"{key}={value}" + for key, value in parameter_input_checker_for_lattice(element).items() + ) + + ")" + for element in state.selectedLatticeList + ) + + return f"lattice_configuration = [\n {lattice_elements}\n]" + + +# ----------------------------------------------------------------------------- +# Trame setup +# ----------------------------------------------------------------------------- + + +def input_file(): + """ + This function creates the template to export + dashboard user inputs into a python script. + """ + script = f""" +from impactx import ImpactX, distribution, elements + +sim = ImpactX() + +sim.particle_shape = {state.particle_shape} +sim.space_charge = False +sim.csr = False +sim.slice_step_diagnostics = True + +sim.init_grids() + +# Initialize particle beam +kin_energy_MeV = {state.kin_energy_MeV} +bunch_charge_C = {state.bunch_charge_C} +npart = {state.npart} + +# Reference particle +ref = sim.particle_container().ref_particle() +ref.set_charge_qe({state.charge_qe}).set_mass_MeV({state.mass_MeV}).set_kin_energy_MeV(kin_energy_MeV) + +{build_distribution_list()} +sim.add_particles(bunch_charge_C, distr, npart) + +{build_lattice_list()} +sim.lattice.extend(lattice_configuration) + +# Simulate +sim.evolve() + +sim.finalize() +""" + + return script diff --git a/src/python/impactx/dashboard/Toolbar/toolbarMain.py b/src/python/impactx/dashboard/Toolbar/toolbarMain.py index a842e6e8b..4b71b0aca 100644 --- a/src/python/impactx/dashboard/Toolbar/toolbarMain.py +++ b/src/python/impactx/dashboard/Toolbar/toolbarMain.py @@ -9,11 +9,22 @@ from trame.widgets import vuetify from ..trame_setup import setup_server +from .exportTemplate import input_file server, state, ctrl = setup_server() state.show_dashboard_alert = True +# ----------------------------------------------------------------------------- +# Triggers +# ----------------------------------------------------------------------------- + + +@ctrl.trigger("export") +def on_export_click(): + return input_file() + + # ----------------------------------------------------------------------------- # Common toolbar elements # ----------------------------------------------------------------------------- @@ -25,6 +36,15 @@ class ToolbarElements: Vuetify UI elements for toolbar. """ + @staticmethod + def export_input_data(): + vuetify.VIcon( + "mdi-download", + style="color: #00313C;", + click="utils.download('impactx_simulation.py', trigger('export'), 'text/plain')", + disabled=("disableRunSimulationButton", True), + ) + @staticmethod def plot_options(): vuetify.VSelect( @@ -75,6 +95,8 @@ def input_toolbar(): """ (ToolbarElements.dashboard_info(),) + vuetify.VSpacer() + ToolbarElements.export_input_data() @staticmethod def run_toolbar(): diff --git a/src/python/impactx/dashboard/requirements.txt b/src/python/impactx/dashboard/requirements.txt index 7eaac07dc..0726649c2 100644 --- a/src/python/impactx/dashboard/requirements.txt +++ b/src/python/impactx/dashboard/requirements.txt @@ -6,3 +6,4 @@ trame-plotly>=3.0.2 trame-router>=2.2.0 trame-vuetify>=2.6.2 trame-xterm>=0.2.1 +wurlitzer>=3.1.1 diff --git a/src/python/impactx/dashboard/Analyze/plot_PhaseSpaceProjections/phaseSpace.py b/src/python/impactx/dashboard/simulation.py similarity index 75% rename from src/python/impactx/dashboard/Analyze/plot_PhaseSpaceProjections/phaseSpace.py rename to src/python/impactx/dashboard/simulation.py index ed027ca71..057304ab1 100644 --- a/src/python/impactx/dashboard/Analyze/plot_PhaseSpaceProjections/phaseSpace.py +++ b/src/python/impactx/dashboard/simulation.py @@ -1,77 +1,81 @@ -""" -This file is part of ImpactX - -Copyright 2024 ImpactX contributors -Authors: Parthib Roy, Axel Huebl -License: BSD-3-Clause-LBNL -""" - -from ...trame_setup import setup_server - -server, state, ctrl = setup_server() - -import base64 -import io - -from impactx import Config, ImpactX - -from ...Input.distributionParameters.distributionMain import distribution_parameters -from ...Input.latticeConfiguration.latticeMain import lattice_elements -from ..plot_PhaseSpaceProjections.phaseSpaceSettings import adjusted_settings_plot - -# Call MPI_Init and MPI_Finalize only once: -if Config.have_mpi: - from mpi4py import MPI # noqa - - -def fig_to_base64(fig): - """ - Puts png in trame-compatible form - """ - buf = io.BytesIO() - fig.savefig(buf, format="png") - buf.seek(0) - return base64.b64encode(buf.read()).decode("utf-8") - - -def run_simulation(): - """ - This tests using ImpactX and Pandas Dataframes - """ - sim = ImpactX() - - sim.particle_shape = state.particle_shape - sim.space_charge = False - sim.slice_step_diagnostics = True - sim.init_grids() - - # init particle beam - kin_energy_MeV = state.kin_energy_MeV - bunch_charge_C = state.bunch_charge_C - npart = state.npart - - # reference particle - pc = sim.particle_container() - ref = pc.ref_particle() - ref.set_charge_qe(-1.0).set_mass_MeV(0.510998950).set_kin_energy_MeV(kin_energy_MeV) - - distribution = distribution_parameters() - sim.add_particles(bunch_charge_C, distribution, npart) - - lattice_configuration = lattice_elements() - - sim.lattice.extend(lattice_configuration) - - # simulate - sim.evolve() - - fig = adjusted_settings_plot(pc) - fig_original = pc.plot_phasespace() - - if fig_original is not None: - image_base64 = fig_to_base64(fig_original) - state.image_data = f"data:image/png;base64, {image_base64}" - - sim.finalize() - - return fig +""" +This file is part of ImpactX + +Copyright 2024 ImpactX contributors +Authors: Parthib Roy, Axel Huebl +License: BSD-3-Clause-LBNL +""" + +from .trame_setup import setup_server + +server, state, ctrl = setup_server() + +import base64 +import io + +from impactx import Config, ImpactX + +from .Analyze.plot_PhaseSpaceProjections.phaseSpaceSettings import ( + adjusted_settings_plot, +) +from .Input.distributionParameters.distributionMain import distribution_parameters +from .Input.latticeConfiguration.latticeMain import lattice_elements + +# Call MPI_Init and MPI_Finalize only once: +if Config.have_mpi: + from mpi4py import MPI # noqa + + +def fig_to_base64(fig): + """ + Puts png in trame-compatible form + """ + buf = io.BytesIO() + fig.savefig(buf, format="png") + buf.seek(0) + return base64.b64encode(buf.read()).decode("utf-8") + + +def run_simulation(): + """ + This tests using ImpactX and Pandas Dataframes + """ + sim = ImpactX() + + sim.particle_shape = state.particle_shape + sim.space_charge = False + sim.slice_step_diagnostics = True + sim.init_grids() + + # init particle beam + kin_energy_MeV = state.kin_energy_MeV + bunch_charge_C = state.bunch_charge_C + npart = state.npart + + # reference particle + pc = sim.particle_container() + ref = pc.ref_particle() + ref.set_charge_qe(state.charge_qe).set_mass_MeV(state.mass_MeV).set_kin_energy_MeV( + kin_energy_MeV + ) + + distribution = distribution_parameters() + sim.add_particles(bunch_charge_C, distribution, npart) + + lattice_configuration = lattice_elements() + + sim.lattice.extend(lattice_configuration) + + # simulate + sim.evolve() + + fig = adjusted_settings_plot(pc) + fig_original = pc.plot_phasespace() + + if fig_original is not None: + image_base64 = fig_to_base64(fig_original) + state.image_data = f"data:image/png;base64, {image_base64}" + + sim.finalize() + + return fig diff --git a/src/python/impactx/distribution_input_helpers.py b/src/python/impactx/distribution_input_helpers.py new file mode 100644 index 000000000..2b0ab4b42 --- /dev/null +++ b/src/python/impactx/distribution_input_helpers.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +# +# Copyright 2024 ImpactX contributors +# Authors: Marco Garten +# License: BSD-3-Clause-LBNL +# +# -*- coding: utf-8 -*- + +import numpy as np + + +def twiss( + beta_x: np.float64, + beta_y: np.float64, + beta_t: np.float64, + emitt_x: np.float64, + emitt_y: np.float64, + emitt_t: np.float64, + alpha_x: np.float64 = 0.0, + alpha_y: np.float64 = 0.0, + alpha_t: np.float64 = 0.0, +): + """ + Helper function to convert Courant-Snyder / Twiss input into phase space ellipse input. + + :param beta_x: Beta function value (unit: meter) in the x dimension, must be a non-zero positive value. + :param beta_y: Beta function value (unit: meter) in the y dimension, must be a non-zero positive value. + :param beta_t: Beta function value (unit: meter) in the t dimension (arrival time differences multiplied by light speed), must be a non-zero positive value. + :param emitt_x: Emittance value (unit: meter times radian) in the x dimension, must be a non-zero positive value. + :param emitt_y: Emittance value (unit: meter times radian) in the y dimension, must be a non-zero positive value. + :param emitt_t: Emittance value (unit: meter times radian) in the t dimension (arrival time differences multiplied by light speed), must be a non-zero positive value. + :param alpha_x: Alpha function value () in the x dimension, default is 0.0. + :param alpha_y: Alpha function value in the y dimension, default is 0.0. + :param alpha_t: Alpha function value in the t dimension, default is 0.0. + :return: A dictionary containing calculated phase space input: 'lambdaX', 'lambdaY', 'lambdaT', 'lambdaPx', 'lambdaPy', 'lambdaPt', 'muxpx', 'muypy', 'mutpt'. + """ + if beta_x <= 0.0 or beta_y <= 0.0 or beta_t <= 0.0: + raise ValueError( + "Input Error: The beta function values need to be non-zero positive values in all dimensions." + ) + + if emitt_x <= 0.0 or emitt_y <= 0.0 or emitt_t <= 0.0: + raise ValueError( + "Input Error: Emittance values need to be non-zero positive values in all dimensions." + ) + + betas = [beta_x, beta_y, beta_t] + alphas = [alpha_x, alpha_y, alpha_t] + + gammas = [] + # calculate Courant-Snyder gammas + for i in range(3): + gammas.append((1 + alphas[i] ** 2) / betas[i]) + gamma_x, gamma_y, gamma_t = gammas + + return { + "lambdaX": np.sqrt(emitt_x / gamma_x), + "lambdaY": np.sqrt(emitt_y / gamma_y), + "lambdaT": np.sqrt(emitt_t / gamma_t), + "lambdaPx": np.sqrt(emitt_x / beta_x), + "lambdaPy": np.sqrt(emitt_y / beta_y), + "lambdaPt": np.sqrt(emitt_t / beta_t), + "muxpx": alpha_x / np.sqrt(beta_x * gamma_x), + "muypy": alpha_y / np.sqrt(beta_y * gamma_y), + "mutpt": alpha_t / np.sqrt(beta_t * gamma_t), + } diff --git a/src/python/impactx/distribution_input_helpers.pyi b/src/python/impactx/distribution_input_helpers.pyi new file mode 100644 index 000000000..2586f8afb --- /dev/null +++ b/src/python/impactx/distribution_input_helpers.pyi @@ -0,0 +1,34 @@ +from __future__ import annotations + +import numpy +import numpy as np + +__all__ = ["np", "twiss"] + +def twiss( + beta_x: numpy.float64, + beta_y: numpy.float64, + beta_t: numpy.float64, + emitt_x: numpy.float64, + emitt_y: numpy.float64, + emitt_t: numpy.float64, + alpha_x: numpy.float64 = 0.0, + alpha_y: numpy.float64 = 0.0, + alpha_t: numpy.float64 = 0.0, +): + """ + + Helper function to convert Courant-Snyder / Twiss input into phase space ellipse input. + + :param beta_x: Beta function value (unit: meter) in the x dimension, must be a non-zero positive value. + :param beta_y: Beta function value (unit: meter) in the y dimension, must be a non-zero positive value. + :param beta_t: Beta function value (unit: meter) in the t dimension (arrival time differences multiplied by light speed), must be a non-zero positive value. + :param emitt_x: Emittance value (unit: meter times radian) in the x dimension, must be a non-zero positive value. + :param emitt_y: Emittance value (unit: meter times radian) in the y dimension, must be a non-zero positive value. + :param emitt_t: Emittance value (unit: meter times radian) in the t dimension (arrival time differences multiplied by light speed), must be a non-zero positive value. + :param alpha_x: Alpha function value () in the x dimension, default is 0.0. + :param alpha_y: Alpha function value in the y dimension, default is 0.0. + :param alpha_t: Alpha function value in the t dimension, default is 0.0. + :return: A dictionary containing calculated phase space input: 'lambdaX', 'lambdaY', 'lambdaT', 'lambdaPx', 'lambdaPy', 'lambdaPt', 'muxpx', 'muypy', 'mutpt'. + + """ diff --git a/src/python/impactx/impactx_pybind/__init__.pyi b/src/python/impactx/impactx_pybind/__init__.pyi index e9220f358..8370f8a2d 100644 --- a/src/python/impactx/impactx_pybind/__init__.pyi +++ b/src/python/impactx/impactx_pybind/__init__.pyi @@ -238,6 +238,13 @@ class ImpactX: @dynamic_size.setter def dynamic_size(self, arg1: bool) -> None: ... @property + def eigenemittances(self) -> bool: + """ + Enable or disable eigenemittance diagnostic calculations (default: disabled). + """ + @eigenemittances.setter + def eigenemittances(self, arg1: bool) -> None: ... + @property def finest_level(self) -> int: """ The currently finest level of mesh-refinement used. This is always less or equal to max_level. @@ -683,6 +690,7 @@ def push( | elements.ExactDrift | elements.ExactSbend | elements.Kicker + | elements.Marker | elements.Multipole | elements.NonlinearLens | elements.Programmable @@ -697,6 +705,7 @@ def push( | elements.TaperedPL | elements.ThinDipole, step: int = 0, + period: int = 0, ) -> None: """ Push particles through an element @@ -706,6 +715,6 @@ __author__: str = ( "Axel Huebl, Chad Mitchell, Ryan Sandberg, Marco Garten, Ji Qiang, et al." ) __license__: str = "BSD-3-Clause-LBNL" -__version__: str = "24.08" +__version__: str = "24.09" s: CoordSystem # value = t: CoordSystem # value = diff --git a/src/python/impactx/impactx_pybind/elements.pyi b/src/python/impactx/impactx_pybind/elements.pyi index dd3427062..67bb1995d 100644 --- a/src/python/impactx/impactx_pybind/elements.pyi +++ b/src/python/impactx/impactx_pybind/elements.pyi @@ -26,7 +26,9 @@ __all__ = [ "ExactSbend", "Kicker", "KnownElementsList", + "Marker", "Multipole", + "Named", "NonlinearLens", "PRot", "Programmable", @@ -70,7 +72,7 @@ class Alignment: @rotation.setter def rotation(self, arg1: float) -> None: ... -class Aperture(Thin, Alignment): +class Aperture(Named, Thin, Alignment): def __init__( self, xmax: float, @@ -79,13 +81,17 @@ class Aperture(Thin, Alignment): dx: float = 0, dy: float = 0, rotation: float = 0, + name: str | None = None, ) -> None: """ A short collimator element applying a transverse aperture boundary. """ def __repr__(self) -> str: ... def push( - self, pc: impactx.impactx_pybind.ImpactXParticleContainer, step: int = 0 + self, + pc: impactx.impactx_pybind.ImpactXParticleContainer, + step: int = 0, + period: int = 0, ) -> None: """ Push first the reference particle, then all other particles. @@ -114,13 +120,20 @@ class Aperture(Thin, Alignment): class BeamMonitor(Thin): def __init__( - self, name: str, backend: str = "default", encoding: str = "g" + self, + name: str, + backend: str = "default", + encoding: str = "g", + period_sample_intervals: int = 1, ) -> None: """ This element writes the particle beam out to openPMD data. """ def push( - self, pc: impactx.impactx_pybind.ImpactXParticleContainer, step: int = 0 + self, + pc: impactx.impactx_pybind.ImpactXParticleContainer, + step: int = 0, + period: int = 0, ) -> None: """ Push first the reference particle, then all other particles. @@ -168,16 +181,25 @@ class BeamMonitor(Thin): @tn.setter def tn(self, arg1: float) -> None: ... -class Buncher(Thin, Alignment): +class Buncher(Named, Thin, Alignment): def __init__( - self, V: float, k: float, dx: float = 0, dy: float = 0, rotation: float = 0 + self, + V: float, + k: float, + dx: float = 0, + dy: float = 0, + rotation: float = 0, + name: str | None = None, ) -> None: """ A short linear RF cavity element at zero-crossing for bunching. """ def __repr__(self) -> str: ... def push( - self, pc: impactx.impactx_pybind.ImpactXParticleContainer, step: int = 0 + self, + pc: impactx.impactx_pybind.ImpactXParticleContainer, + step: int = 0, + period: int = 0, ) -> None: """ Push first the reference particle, then all other particles. @@ -197,7 +219,7 @@ class Buncher(Thin, Alignment): @k.setter def k(self, arg1: float) -> None: ... -class CFbend(Thick, Alignment): +class CFbend(Named, Thick, Alignment): def __init__( self, ds: float, @@ -207,13 +229,17 @@ class CFbend(Thick, Alignment): dy: float = 0, rotation: float = 0, nslice: int = 1, + name: str | None = None, ) -> None: """ An ideal combined function bend (sector bend with quadrupole component). """ def __repr__(self) -> str: ... def push( - self, pc: impactx.impactx_pybind.ImpactXParticleContainer, step: int = 0 + self, + pc: impactx.impactx_pybind.ImpactXParticleContainer, + step: int = 0, + period: int = 0, ) -> None: """ Push first the reference particle, then all other particles. @@ -233,7 +259,7 @@ class CFbend(Thick, Alignment): @rc.setter def rc(self, arg1: float) -> None: ... -class ChrAcc(Thick, Alignment): +class ChrAcc(Named, Thick, Alignment): def __init__( self, ds: float, @@ -243,13 +269,17 @@ class ChrAcc(Thick, Alignment): dy: float = 0, rotation: float = 0, nslice: int = 1, + name: str | None = None, ) -> None: """ A region of Uniform Acceleration, with chromatic effects included. """ def __repr__(self) -> str: ... def push( - self, pc: impactx.impactx_pybind.ImpactXParticleContainer, step: int = 0 + self, + pc: impactx.impactx_pybind.ImpactXParticleContainer, + step: int = 0, + period: int = 0, ) -> None: """ Push first the reference particle, then all other particles. @@ -269,7 +299,7 @@ class ChrAcc(Thick, Alignment): @ez.setter def ez(self, arg1: float) -> None: ... -class ChrDrift(Thick, Alignment): +class ChrDrift(Named, Thick, Alignment): def __init__( self, ds: float, @@ -277,19 +307,23 @@ class ChrDrift(Thick, Alignment): dy: float = 0, rotation: float = 0, nslice: int = 1, + name: str | None = None, ) -> None: """ A Drift with chromatic effects included. """ def __repr__(self) -> str: ... def push( - self, pc: impactx.impactx_pybind.ImpactXParticleContainer, step: int = 0 + self, + pc: impactx.impactx_pybind.ImpactXParticleContainer, + step: int = 0, + period: int = 0, ) -> None: """ Push first the reference particle, then all other particles. """ -class ChrPlasmaLens(Thick, Alignment): +class ChrPlasmaLens(Named, Thick, Alignment): def __init__( self, ds: float, @@ -299,13 +333,17 @@ class ChrPlasmaLens(Thick, Alignment): dy: float = 0, rotation: float = 0, nslice: int = 1, + name: str | None = None, ) -> None: """ An active Plasma Lens with chromatic effects included. """ def __repr__(self) -> str: ... def push( - self, pc: impactx.impactx_pybind.ImpactXParticleContainer, step: int = 0 + self, + pc: impactx.impactx_pybind.ImpactXParticleContainer, + step: int = 0, + period: int = 0, ) -> None: """ Push first the reference particle, then all other particles. @@ -325,7 +363,7 @@ class ChrPlasmaLens(Thick, Alignment): @unit.setter def unit(self, arg1: int) -> None: ... -class ChrQuad(Thick, Alignment): +class ChrQuad(Named, Thick, Alignment): def __init__( self, ds: float, @@ -335,13 +373,17 @@ class ChrQuad(Thick, Alignment): dy: float = 0, rotation: float = 0, nslice: int = 1, + name: str | None = None, ) -> None: """ A Quadrupole magnet with chromatic effects included. """ def __repr__(self) -> str: ... def push( - self, pc: impactx.impactx_pybind.ImpactXParticleContainer, step: int = 0 + self, + pc: impactx.impactx_pybind.ImpactXParticleContainer, + step: int = 0, + period: int = 0, ) -> None: """ Push first the reference particle, then all other particles. @@ -361,7 +403,7 @@ class ChrQuad(Thick, Alignment): @unit.setter def unit(self, arg1: int) -> None: ... -class ConstF(Thick, Alignment): +class ConstF(Named, Thick, Alignment): def __init__( self, ds: float, @@ -372,13 +414,17 @@ class ConstF(Thick, Alignment): dy: float = 0, rotation: float = 0, nslice: int = 1, + name: str | None = None, ) -> None: """ A linear Constant Focusing element. """ def __repr__(self) -> str: ... def push( - self, pc: impactx.impactx_pybind.ImpactXParticleContainer, step: int = 0 + self, + pc: impactx.impactx_pybind.ImpactXParticleContainer, + step: int = 0, + period: int = 0, ) -> None: """ Push first the reference particle, then all other particles. @@ -405,7 +451,7 @@ class ConstF(Thick, Alignment): @ky.setter def ky(self, arg1: float) -> None: ... -class DipEdge(Thin, Alignment): +class DipEdge(Named, Thin, Alignment): def __init__( self, psi: float, @@ -415,13 +461,17 @@ class DipEdge(Thin, Alignment): dx: float = 0, dy: float = 0, rotation: float = 0, + name: str | None = None, ) -> None: """ Edge focusing associated with bend entry or exit. """ def __repr__(self) -> str: ... def push( - self, pc: impactx.impactx_pybind.ImpactXParticleContainer, step: int = 0 + self, + pc: impactx.impactx_pybind.ImpactXParticleContainer, + step: int = 0, + period: int = 0, ) -> None: """ Push first the reference particle, then all other particles. @@ -455,7 +505,7 @@ class DipEdge(Thin, Alignment): @rc.setter def rc(self, arg1: float) -> None: ... -class Drift(Thick, Alignment): +class Drift(Named, Thick, Alignment): def __init__( self, ds: float, @@ -463,13 +513,17 @@ class Drift(Thick, Alignment): dy: float = 0, rotation: float = 0, nslice: int = 1, + name: str | None = None, ) -> None: """ A drift. """ def __repr__(self) -> str: ... def push( - self, pc: impactx.impactx_pybind.ImpactXParticleContainer, step: int = 0 + self, + pc: impactx.impactx_pybind.ImpactXParticleContainer, + step: int = 0, + period: int = 0, ) -> None: """ Push first the reference particle, then all other particles. @@ -482,13 +536,16 @@ class Empty(Thin): """ def __repr__(self) -> str: ... def push( - self, pc: impactx.impactx_pybind.ImpactXParticleContainer, step: int = 0 + self, + pc: impactx.impactx_pybind.ImpactXParticleContainer, + step: int = 0, + period: int = 0, ) -> None: """ Push first the reference particle, then all other particles. """ -class ExactDrift(Thick, Alignment): +class ExactDrift(Named, Thick, Alignment): def __init__( self, ds: float, @@ -496,19 +553,23 @@ class ExactDrift(Thick, Alignment): dy: float = 0, rotation: float = 0, nslice: int = 1, + name: str | None = None, ) -> None: """ A Drift using the exact nonlinear map. """ def __repr__(self) -> str: ... def push( - self, pc: impactx.impactx_pybind.ImpactXParticleContainer, step: int = 0 + self, + pc: impactx.impactx_pybind.ImpactXParticleContainer, + step: int = 0, + period: int = 0, ) -> None: """ Push first the reference particle, then all other particles. """ -class ExactSbend(Thick, Alignment): +class ExactSbend(Named, Thick, Alignment): def __init__( self, ds: float, @@ -518,13 +579,17 @@ class ExactSbend(Thick, Alignment): dy: float = 0, rotation: float = 0, nslice: int = 1, + name: str | None = None, ) -> None: """ An ideal sector bend using the exact nonlinear map. When B = 0, the reference bending radius is defined by r0 = length / (angle in rad), corresponding to a magnetic field of B = rigidity / r0; otherwise the reference bending radius is defined by r0 = rigidity / B. """ def __repr__(self) -> str: ... def push( - self, pc: impactx.impactx_pybind.ImpactXParticleContainer, step: int = 0 + self, + pc: impactx.impactx_pybind.ImpactXParticleContainer, + step: int = 0, + period: int = 0, ) -> None: """ Push first the reference particle, then all other particles. @@ -544,7 +609,7 @@ class ExactSbend(Thick, Alignment): @phi.setter def phi(self, arg1: float) -> None: ... -class Kicker(Thin, Alignment): +class Kicker(Named, Thin, Alignment): def __init__( self, xkick: float, @@ -553,13 +618,17 @@ class Kicker(Thin, Alignment): dx: float = 0, dy: float = 0, rotation: float = 0, + name: str | None = None, ) -> None: """ A thin transverse kicker element. Kicks are for unit "dimensionless" or in "T-m". """ def __repr__(self) -> str: ... def push( - self, pc: impactx.impactx_pybind.ImpactXParticleContainer, step: int = 0 + self, + pc: impactx.impactx_pybind.ImpactXParticleContainer, + step: int = 0, + period: int = 0, ) -> None: """ Push first the reference particle, then all other particles. @@ -600,6 +669,7 @@ class KnownElementsList: | ExactDrift | ExactSbend | Kicker + | Marker | Multipole | NonlinearLens | Programmable @@ -634,6 +704,7 @@ class KnownElementsList: | ExactDrift | ExactSbend | Kicker + | Marker | Multipole | NonlinearLens | Programmable @@ -669,6 +740,7 @@ class KnownElementsList: | ExactDrift | ExactSbend | Kicker + | Marker | Multipole | NonlinearLens | Programmable @@ -706,7 +778,23 @@ class KnownElementsList: Return and remove the last element of the list. """ -class Multipole(Thin, Alignment): +class Marker(Named, Thin): + def __init__(self, arg0: str) -> None: + """ + This named element does nothing. + """ + def __repr__(self) -> str: ... + def push( + self, + pc: impactx.impactx_pybind.ImpactXParticleContainer, + step: int = 0, + period: int = 0, + ) -> None: + """ + Push first the reference particle, then all other particles. + """ + +class Multipole(Named, Thin, Alignment): def __init__( self, multipole: int, @@ -715,13 +803,17 @@ class Multipole(Thin, Alignment): dx: float = 0, dy: float = 0, rotation: float = 0, + name: str | None = None, ) -> None: """ A general thin multipole element. """ def __repr__(self) -> str: ... def push( - self, pc: impactx.impactx_pybind.ImpactXParticleContainer, step: int = 0 + self, + pc: impactx.impactx_pybind.ImpactXParticleContainer, + step: int = 0, + period: int = 0, ) -> None: """ Push first the reference particle, then all other particles. @@ -748,7 +840,18 @@ class Multipole(Thin, Alignment): @multipole.setter def multipole(self, arg1: float) -> None: ... -class NonlinearLens(Thin, Alignment): +class Named: + @property + def has_name(self) -> bool: ... + @property + def name(self) -> str: + """ + segment length in m + """ + @name.setter + def name(self, arg1: str) -> None: ... + +class NonlinearLens(Named, Thin, Alignment): def __init__( self, knll: float, @@ -756,13 +859,17 @@ class NonlinearLens(Thin, Alignment): dx: float = 0, dy: float = 0, rotation: float = 0, + name: str | None = None, ) -> None: """ Single short segment of the nonlinear magnetic insert element. """ def __repr__(self) -> str: ... def push( - self, pc: impactx.impactx_pybind.ImpactXParticleContainer, step: int = 0 + self, + pc: impactx.impactx_pybind.ImpactXParticleContainer, + step: int = 0, + period: int = 0, ) -> None: """ Push first the reference particle, then all other particles. @@ -782,14 +889,17 @@ class NonlinearLens(Thin, Alignment): @knll.setter def knll(self, arg1: float) -> None: ... -class PRot(Thin): - def __init__(self, phi_in: float, phi_out: float) -> None: +class PRot(Named, Thin): + def __init__(self, phi_in: float, phi_out: float, name: str | None = None) -> None: """ An exact pole-face rotation in the x-z plane. Both angles are in degrees. """ def __repr__(self) -> str: ... def push( - self, pc: impactx.impactx_pybind.ImpactXParticleContainer, step: int = 0 + self, + pc: impactx.impactx_pybind.ImpactXParticleContainer, + step: int = 0, + period: int = 0, ) -> None: """ Push first the reference particle, then all other particles. @@ -809,10 +919,12 @@ class PRot(Thin): @phi_out.setter def phi_out(self, arg1: float) -> None: ... -class Programmable: +class Programmable(Named): ds: float nslice: int - def __init__(self, ds: float = 0.0, nslice: int = 1) -> None: + def __init__( + self, ds: float = 0.0, nslice: int = 1, name: str | None = None + ) -> None: """ A programmable beam optics element. """ @@ -837,15 +949,17 @@ class Programmable: @property def push( self, - ) -> typing.Callable[[impactx.impactx_pybind.ImpactXParticleContainer, int], None]: + ) -> typing.Callable[ + [impactx.impactx_pybind.ImpactXParticleContainer, int, int], None + ]: """ - hook for push of whole container (pc, step) + hook for push of whole container (pc, step, period) """ @push.setter def push( self, arg1: typing.Callable[ - [impactx.impactx_pybind.ImpactXParticleContainer, int], None + [impactx.impactx_pybind.ImpactXParticleContainer, int, int], None ], ) -> None: ... @property @@ -865,7 +979,7 @@ class Programmable: @threadsafe.setter def threadsafe(self, arg1: bool) -> None: ... -class Quad(Thick, Alignment): +class Quad(Named, Thick, Alignment): def __init__( self, ds: float, @@ -874,13 +988,17 @@ class Quad(Thick, Alignment): dy: float = 0, rotation: float = 0, nslice: int = 1, + name: str | None = None, ) -> None: """ A Quadrupole magnet. """ def __repr__(self) -> str: ... def push( - self, pc: impactx.impactx_pybind.ImpactXParticleContainer, step: int = 0 + self, + pc: impactx.impactx_pybind.ImpactXParticleContainer, + step: int = 0, + period: int = 0, ) -> None: """ Push first the reference particle, then all other particles. @@ -893,7 +1011,7 @@ class Quad(Thick, Alignment): @k.setter def k(self, arg1: float) -> None: ... -class RFCavity(Thick, Alignment): +class RFCavity(Named, Thick, Alignment): def __init__( self, ds: float, @@ -907,13 +1025,17 @@ class RFCavity(Thick, Alignment): rotation: float = 0, mapsteps: int = 1, nslice: int = 1, + name: str | None = None, ) -> None: """ An RF cavity. """ def __repr__(self) -> str: ... def push( - self, pc: impactx.impactx_pybind.ImpactXParticleContainer, step: int = 0 + self, + pc: impactx.impactx_pybind.ImpactXParticleContainer, + step: int = 0, + period: int = 0, ) -> None: """ Push first the reference particle, then all other particles. @@ -947,7 +1069,7 @@ class RFCavity(Thick, Alignment): @phase.setter def phase(self, arg1: float) -> None: ... -class Sbend(Thick, Alignment): +class Sbend(Named, Thick, Alignment): def __init__( self, ds: float, @@ -956,13 +1078,17 @@ class Sbend(Thick, Alignment): dy: float = 0, rotation: float = 0, nslice: int = 1, + name: str | None = None, ) -> None: """ An ideal sector bend. """ def __repr__(self) -> str: ... def push( - self, pc: impactx.impactx_pybind.ImpactXParticleContainer, step: int = 0 + self, + pc: impactx.impactx_pybind.ImpactXParticleContainer, + step: int = 0, + period: int = 0, ) -> None: """ Push first the reference particle, then all other particles. @@ -975,7 +1101,7 @@ class Sbend(Thick, Alignment): @rc.setter def rc(self, arg1: float) -> None: ... -class ShortRF(Thin, Alignment): +class ShortRF(Named, Thin, Alignment): def __init__( self, V: float, @@ -984,13 +1110,17 @@ class ShortRF(Thin, Alignment): dx: float = 0, dy: float = 0, rotation: float = 0, + name: str | None = None, ) -> None: """ A short RF cavity element. """ def __repr__(self) -> str: ... def push( - self, pc: impactx.impactx_pybind.ImpactXParticleContainer, step: int = 0 + self, + pc: impactx.impactx_pybind.ImpactXParticleContainer, + step: int = 0, + period: int = 0, ) -> None: """ Push first the reference particle, then all other particles. @@ -1017,7 +1147,7 @@ class ShortRF(Thin, Alignment): @phase.setter def phase(self, arg1: float) -> None: ... -class SoftQuadrupole(Thick, Alignment): +class SoftQuadrupole(Named, Thick, Alignment): def __init__( self, ds: float, @@ -1029,13 +1159,17 @@ class SoftQuadrupole(Thick, Alignment): rotation: float = 0, mapsteps: int = 1, nslice: int = 1, + name: str | None = None, ) -> None: """ A soft-edge quadrupole. """ def __repr__(self) -> str: ... def push( - self, pc: impactx.impactx_pybind.ImpactXParticleContainer, step: int = 0 + self, + pc: impactx.impactx_pybind.ImpactXParticleContainer, + step: int = 0, + period: int = 0, ) -> None: """ Push first the reference particle, then all other particles. @@ -1055,7 +1189,7 @@ class SoftQuadrupole(Thick, Alignment): @mapsteps.setter def mapsteps(self, arg1: int) -> None: ... -class SoftSolenoid(Thick, Alignment): +class SoftSolenoid(Named, Thick, Alignment): def __init__( self, ds: float, @@ -1068,13 +1202,17 @@ class SoftSolenoid(Thick, Alignment): rotation: float = 0, mapsteps: int = 1, nslice: int = 1, + name: str | None = None, ) -> None: """ A soft-edge solenoid. """ def __repr__(self) -> str: ... def push( - self, pc: impactx.impactx_pybind.ImpactXParticleContainer, step: int = 0 + self, + pc: impactx.impactx_pybind.ImpactXParticleContainer, + step: int = 0, + period: int = 0, ) -> None: """ Push first the reference particle, then all other particles. @@ -1101,7 +1239,7 @@ class SoftSolenoid(Thick, Alignment): @unit.setter def unit(self, arg1: float) -> None: ... -class Sol(Thick, Alignment): +class Sol(Named, Thick, Alignment): def __init__( self, ds: float, @@ -1110,13 +1248,17 @@ class Sol(Thick, Alignment): dy: float = 0, rotation: float = 0, nslice: int = 1, + name: str | None = None, ) -> None: """ An ideal hard-edge Solenoid magnet. """ def __repr__(self) -> str: ... def push( - self, pc: impactx.impactx_pybind.ImpactXParticleContainer, step: int = 0 + self, + pc: impactx.impactx_pybind.ImpactXParticleContainer, + step: int = 0, + period: int = 0, ) -> None: """ Push first the reference particle, then all other particles. @@ -1129,7 +1271,7 @@ class Sol(Thick, Alignment): @ks.setter def ks(self, arg1: float) -> None: ... -class TaperedPL(Thin, Alignment): +class TaperedPL(Named, Thin, Alignment): def __init__( self, k: float, @@ -1138,6 +1280,7 @@ class TaperedPL(Thin, Alignment): dx: float = 0, dy: float = 0, rotation: float = 0, + name: str | None = None, ) -> None: """ A thin nonlinear plasma lens with transverse (horizontal) taper @@ -1150,7 +1293,10 @@ class TaperedPL(Thin, Alignment): """ def __repr__(self) -> str: ... def push( - self, pc: impactx.impactx_pybind.ImpactXParticleContainer, step: int = 0 + self, + pc: impactx.impactx_pybind.ImpactXParticleContainer, + step: int = 0, + period: int = 0, ) -> None: """ Push first the reference particle, then all other particles. @@ -1213,16 +1359,25 @@ class Thin: number of slices used for the application of space charge """ -class ThinDipole(Thin, Alignment): +class ThinDipole(Named, Thin, Alignment): def __init__( - self, theta: float, rc: float, dx: float = 0, dy: float = 0, rotation: float = 0 + self, + theta: float, + rc: float, + dx: float = 0, + dy: float = 0, + rotation: float = 0, + name: str | None = None, ) -> None: """ A thin kick model of a dipole bend. """ def __repr__(self) -> str: ... def push( - self, pc: impactx.impactx_pybind.ImpactXParticleContainer, step: int = 0 + self, + pc: impactx.impactx_pybind.ImpactXParticleContainer, + step: int = 0, + period: int = 0, ) -> None: """ Push first the reference particle, then all other particles. diff --git a/src/python/impactx/madx_to_impactx.py b/src/python/impactx/madx_to_impactx.py index 56a8881b9..59c1da1bb 100644 --- a/src/python/impactx/madx_to_impactx.py +++ b/src/python/impactx/madx_to_impactx.py @@ -66,22 +66,27 @@ def lattice(parsed_beamline, nslice=1): # print(d) if d["type"] in [k.casefold() for k in list(madx_to_impactx_dict.keys())]: if d["type"] == "drift": - impactx_beamline.append(elements.Drift(ds=d["l"], nslice=nslice)) + impactx_beamline.append( + elements.Drift(name=d["name"], ds=d["l"], nslice=nslice) + ) elif d["type"] == "quadrupole": impactx_beamline.append( - elements.Quad(ds=d["l"], k=d["k1"], nslice=nslice) + elements.Quad(name=d["name"], ds=d["l"], k=d["k1"], nslice=nslice) ) elif d["type"] == "sbend": impactx_beamline.append( - elements.Sbend(ds=d["l"], rc=d["l"] / d["angle"], nslice=nslice) + elements.Sbend( + name=d["name"], ds=d["l"], rc=d["l"] / d["angle"], nslice=nslice + ) ) elif d["type"] == "solenoid": impactx_beamline.append( - elements.Sol(ds=d["l"], ks=d["ks"], nslice=nslice) + elements.Sol(name=d["name"], ds=d["l"], ks=d["ks"], nslice=nslice) ) elif d["type"] == "dipedge": impactx_beamline.append( elements.DipEdge( + name=d["name"], psi=d["e1"], rc=1.0 / d["h"], # MAD-X is using half the gap height @@ -92,14 +97,21 @@ def lattice(parsed_beamline, nslice=1): elif d["type"] == "kicker": impactx_beamline.append( elements.Kicker( + name=d["name"], xkick=d["hkick"], ykick=d["vkick"], ) ) elif d["type"] == "monitor": if d["l"] > 0: - impactx_beamline.append(elements.Drift(ds=d["l"], nslice=nslice)) - impactx_beamline.append(elements.BeamMonitor("monitor", backend="h5")) + impactx_beamline.append( + elements.Drift( + name=d["name"] + "_drift", ds=d["l"], nslice=nslice + ) + ) + impactx_beamline.append( + elements.BeamMonitor(name="monitor", backend="h5") + ) # TODO: use name=d["name"] ? else: raise NotImplementedError( "The beamline element named ", diff --git a/tests/python/test_dataframe.py b/tests/python/test_dataframe.py index 42dc0bb4c..db73ab530 100644 --- a/tests/python/test_dataframe.py +++ b/tests/python/test_dataframe.py @@ -57,11 +57,11 @@ def test_df_pandas(save_png=True): # init accelerator lattice fodo = [ - elements.Drift(0.25), - elements.Quad(1.0, 1.0), - elements.Drift(0.5), - elements.Quad(1.0, -1.0), - elements.Drift(0.25), + elements.Drift(name="d1", ds=0.25), + elements.Quad(name="q1", ds=1.0, k=1.0), + elements.Drift(name="d2", ds=0.5), + elements.Quad(name="q2", ds=1.0, k=-1.0), + elements.Drift(name="d3", ds=0.25), ] sim.lattice.extend(fodo) diff --git a/tests/python/test_impactx.py b/tests/python/test_impactx.py index d0b086a59..871746770 100755 --- a/tests/python/test_impactx.py +++ b/tests/python/test_impactx.py @@ -112,11 +112,11 @@ def test_impactx_nofile(): # init accelerator lattice fodo = [ - elements.Drift(0.25), - elements.Quad(1.0, 1.0), - elements.Drift(0.5), - elements.Quad(1.0, -1.0), - elements.Drift(0.25), + elements.Drift(name="d1", ds=0.25), + elements.Quad(name="q1", ds=1.0, k=1.0), + elements.Drift(name="d2", ds=0.5), + elements.Quad(name="q2", ds=1.0, k=-1.0), + elements.Drift(name="d3", ds=0.25), ] # assign a fodo segment # sim.lattice = fodo @@ -127,7 +127,7 @@ def test_impactx_nofile(): # add 2 more drifts for i in range(4): - sim.lattice.append(elements.Drift(0.25)) + sim.lattice.append(elements.Drift(name="d" + str(4 + i), ds=0.25)) print(sim.lattice) print(len(sim.lattice)) @@ -158,7 +158,7 @@ def test_impactx_noparticles(): # particle bunch: init intentionally missing # init accelerator lattice - sim.lattice.append(elements.Drift(0.5)) + sim.lattice.append(elements.Drift(ds=0.5)) with pytest.raises( RuntimeError, match="No particles found. Cannot run evolve without a beam." @@ -225,7 +225,7 @@ def test_impactx_resting_refparticle(): ): sim.add_particles(bunch_charge=0.0, distr=gaussian, npart=10) - sim.lattice.append(elements.Drift(0.25)) + sim.lattice.append(elements.Drift(ds=0.25)) with pytest.raises( RuntimeError, diff --git a/tests/python/test_particle_tiles.py b/tests/python/test_particle_tiles.py index da7f2d40f..4460530e8 100644 --- a/tests/python/test_particle_tiles.py +++ b/tests/python/test_particle_tiles.py @@ -50,11 +50,11 @@ def test_particle_tiles(): # init accelerator lattice fodo = [ - elements.Drift(0.25), - elements.Quad(1.0, 1.0), - elements.Drift(0.5), - elements.Quad(1.0, -1.0), - elements.Drift(0.25), + elements.Drift(name="d1", ds=0.25), + elements.Quad(name="q1", ds=1.0, k=1.0), + elements.Drift(name="d2", ds=0.5), + elements.Quad(name="q2", ds=1.0, k=-1.0), + elements.Drift(name="d3", ds=0.25), ] sim.lattice.extend(fodo) diff --git a/tests/python/test_push.py b/tests/python/test_push.py index 3c4cc962a..04fde43bd 100644 --- a/tests/python/test_push.py +++ b/tests/python/test_push.py @@ -47,20 +47,26 @@ def test_element_push(): assert pc.total_number_of_particles() == npart # init accelerator lattice + drift = elements.Drift(name="drift1", ds=0.25) + assert drift.name == "drift1" + # changed my mind on the name + drift.name = "mydrift" + assert drift.name == "mydrift" + fodo = [ - elements.Drift(0.25), + drift, ] sim.lattice.extend(fodo) sim.evolve() - # Push manually through a few elements - elements.Quad(1.0, 1.0).push(pc) - elements.Drift(0.5).push(pc) - elements.Quad(1.0, -1.0).push(pc) + # Push manually through a few (unnamed) elements + elements.Quad(ds=1.0, k=1.0).push(pc) + elements.Drift(ds=0.5).push(pc) + elements.Quad(ds=1.0, k=-1.0).push(pc) # alternative formulation - push(pc, elements.Drift(0.25)) + push(pc, elements.Drift(ds=0.25)) # finalize simulation sim.finalize() diff --git a/tests/python/test_transformation.py b/tests/python/test_transformation.py index 93b52966c..ad660564c 100644 --- a/tests/python/test_transformation.py +++ b/tests/python/test_transformation.py @@ -81,7 +81,7 @@ def test_transformation(): for key, val in rbc_s0.items(): if not np.isclose(val, rbc_s[key], rtol=rtol, atol=atol): print(f"initial[{key}]={val}, final[{key}]={rbc_s[key]} not equal") - assert np.isclose(val, rbc_s[key], rtol=rtol, atol=atol) + assert False # assert that the t-based beam is different, at least in the following keys: large_st_diff_keys = [ "beta_x", diff --git a/tests/python/test_xopt.py b/tests/python/test_xopt.py index c5458576a..2862d6bf9 100644 --- a/tests/python/test_xopt.py +++ b/tests/python/test_xopt.py @@ -45,13 +45,13 @@ def build_lattice(parameters: dict, write_particles: bool) -> list: # enforce a mirror symmetry of the triplet line = [ - elements.Drift(ds=2.7, nslice=ns), - elements.Quad(ds=0.1, k=q1_k, nslice=ns), - elements.Drift(ds=1.4, nslice=ns), - elements.Quad(ds=0.2, k=q2_k, nslice=ns), - elements.Drift(ds=1.4, nslice=ns), - elements.Quad(ds=0.1, k=q1_k, nslice=ns), - elements.Drift(ds=2.7, nslice=ns), + elements.Drift("d1", ds=2.7, nslice=ns), + elements.Quad("q1", ds=0.1, k=q1_k, nslice=ns), + elements.Drift("d2", ds=1.4, nslice=ns), + elements.Quad("q2", ds=0.2, k=q2_k, nslice=ns), + elements.Drift("d3", ds=1.4, nslice=ns), + elements.Quad("q1", ds=0.1, k=q1_k, nslice=ns), + elements.Drift("d4", ds=2.7, nslice=ns), ] if write_particles: