diff --git a/docs/source/index.rst b/docs/source/index.rst index 8f02e884d..cada50593 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -65,9 +65,9 @@ Usage :hidden: usage/how_to_run + usage/examples usage/python usage/parameters - usage/examples usage/workflows Data Analysis diff --git a/docs/source/usage/examples.rst b/docs/source/usage/examples.rst index 01606341b..8ef88ef7e 100644 --- a/docs/source/usage/examples.rst +++ b/docs/source/usage/examples.rst @@ -13,6 +13,7 @@ This section allows you to **download input files** that correspond to different examples/cfchannel/README.rst examples/expanding_beam/README.rst examples/kurth/README.rst + examples/alignment/README.rst examples/rfcavity/README.rst examples/fodo_rf/README.rst examples/fodo_chromatic/README.rst diff --git a/docs/source/usage/parameters.rst b/docs/source/usage/parameters.rst index 7b873b72f..6c473811e 100644 --- a/docs/source/usage/parameters.rst +++ b/docs/source/usage/parameters.rst @@ -276,22 +276,24 @@ Lattice Elements * ``cfbend`` for a combined function bending magnet. This requires these additional parameters: * ``.ds`` (``float``, in meters) the segment length - * ``.rc`` (``float``, in meters) the bend radius - * ``.k`` (``float``, in inverse meters squared) the quadrupole strength - = (magnetic field gradient in T/m) / (magnetic rigidity in T-m) * k > 0 horizontal focusing * k < 0 horizontal defocusing + * ``.dx`` (``float``, in meters) horizontal translation error + * ``.dy`` (``float``, in meters) vertical translation error + * ``.rotation`` (``float``, in degrees) rotation error in the transverse plane * ``.nslice`` (``integer``) number of slices used for the application of space charge (default: ``1``) * ``drift`` for a free drift. This requires these additional parameters: * ``.ds`` (``float``, in meters) the segment length - + * ``.dx`` (``float``, in meters) horizontal translation error + * ``.dy`` (``float``, in meters) vertical translation error + * ``.rotation`` (``float``, in degrees) rotation error in the transverse plane * ``.nslice`` (``integer``) number of slices used for the application of space charge (default: ``1``) * ``drift_chromatic`` for a free drift, with chromatic effects included. @@ -299,26 +301,31 @@ Lattice Elements This requires these additional parameters: * ``.ds`` (``float``, in meters) the segment length - + * ``.dx`` (``float``, in meters) horizontal translation error + * ``.dy`` (``float``, in meters) vertical translation error + * ``.rotation`` (``float``, in degrees) rotation error in the transverse plane * ``.nslice`` (``integer``) number of slices used for the application of space charge (default: ``1``) * ``drift_exact`` for a free drift, using the exact nonlinear map. This requires these additional parameters: * ``.ds`` (``float``, in meters) the segment length - + * ``.dx`` (``float``, in meters) horizontal translation error + * ``.dy`` (``float``, in meters) vertical translation error + * ``.rotation`` (``float``, in degrees) rotation error in the transverse plane * ``.nslice`` (``integer``) number of slices used for the application of space charge (default: ``1``) * ``quad`` for a quadrupole. This requires these additional parameters: * ``.ds`` (``float``, in meters) the segment length - * ``.k`` (``float``, in inverse meters squared) the quadrupole strength - = (magnetic field gradient in T/m) / (magnetic rigidity in T-m) * k > 0 horizontal focusing * k < 0 horizontal defocusing + * ``.dx`` (``float``, in meters) horizontal translation error + * ``.dy`` (``float``, in meters) vertical translation error + * ``.rotation`` (``float``, in degrees) rotation error in the transverse plane * ``.nslice`` (``integer``) number of slices used for the application of space charge (default: ``1``) * ``quad_chromatic`` for A Quadrupole magnet, with chromatic effects included. @@ -326,9 +333,7 @@ Lattice Elements This requires these additional parameters: * ``.ds`` (``float``, in meters) the segment length - * ``.k`` (``float``, in inverse meters squared OR in T/m) the quadrupole strength - = (magnetic field gradient in T/m) / (magnetic rigidity in T-m) - if units = 0 OR = magnetic field gradient in T/m - if units = 1 @@ -337,32 +342,33 @@ Lattice Elements * k < 0 horizontal defocusing * ``.units`` (``integer``) specification of units (default: ``0``) - + * ``.dx`` (``float``, in meters) horizontal translation error + * ``.dy`` (``float``, in meters) vertical translation error + * ``.rotation`` (``float``, in degrees) rotation error in the transverse plane * ``.nslice`` (``integer``) number of slices used for the application of space charge (default: ``1``) * ``quadrupole_softedge`` for a soft-edge quadrupole. This requires these additional parameters: * ``.ds`` (``float``, in meters) the segment length - * ``.gscale`` (``float``, in inverse meters) Scaling factor for on-axis magnetic field gradient - * ``.cos_coefficients`` (array of ``float``) cos coefficients in Fourier expansion of the on-axis field gradient (optional); default is a tanh fringe field model from `MaryLie 3.0 `__ - * ``.sin_coefficients`` (array of ``float``) sin coefficients in Fourier expansion of the on-axis field gradient (optional); default is a tanh fringe field model from `MaryLie 3.0 `__ - + * ``.dx`` (``float``, in meters) horizontal translation error + * ``.dy`` (``float``, in meters) vertical translation error + * ``.rotation`` (``float``, in degrees) rotation error in the transverse plane * ``.mapsteps`` (``integer``) number of integration steps per slice used for map and reference particle push in applied fields (default: ``1``) - * ``.nslice`` (``integer``) number of slices used for the application of space charge (default: ``1``) * ``sbend`` for a bending magnet. This requires these additional parameters: * ``.ds`` (``float``, in meters) the segment length - * ``.rc`` (``float``, in meters) the bend radius - + * ``.dx`` (``float``, in meters) horizontal translation error + * ``.dy`` (``float``, in meters) vertical translation error + * ``.rotation`` (``float``, in degrees) rotation error in the transverse plane * ``.nslice`` (``integer``) number of slices used for the application of space charge (default: ``1``) * ``sbend_exact`` for a bending magnet using the exact nonlinear map for the bend body. The map corresponds to the map described in: @@ -371,102 +377,91 @@ Lattice Elements particle. This requires these additional parameters: * ``.ds`` (``float``, in meters) the segment length - * ``.phi`` (``float``, in degrees) the bend angle - * ``.B`` (``float``, in Tesla) the bend magnetic field; when B = 0 (default), 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 - + * ``.dx`` (``float``, in meters) horizontal translation error + * ``.dy`` (``float``, in meters) vertical translation error + * ``.rotation`` (``float``, in degrees) rotation error in the transverse plane * ``.nslice`` (``integer``) number of slices used for the application of space charge (default: ``1``) * ``solenoid`` for an ideal hard-edge solenoid magnet. This requires these additional parameters: * ``.ds`` (``float``, in meters) the segment length - * ``.ks`` (``float``, in meters) Solenoid strength in m^(-1) (MADX convention) - = (magnetic field Bz in T) / (rigidity in T-m) - + * ``.dx`` (``float``, in meters) horizontal translation error + * ``.dy`` (``float``, in meters) vertical translation error + * ``.rotation`` (``float``, in degrees) rotation error in the transverse plane * ``.nslice`` (``integer``) number of slices used for the application of space charge (default: ``1``) * ``solenoid_softedge`` for a soft-edge solenoid. This requires these additional parameters: * ``.ds`` (``float``, in meters) the segment length - * ``.bscale`` (``float``, in inverse meters) Scaling factor for on-axis magnetic field Bz - * ``.cos_coefficients`` (array of ``float``) cos coefficients in Fourier expansion of the on-axis magnetic field Bz (optional); default is a thin-shell model from `DOI:10.1016/J.NIMA.2022.166706 `__ - * ``.sin_coefficients`` (array of ``float``) sin coefficients in Fourier expansion of the on-axis magnetic field Bz (optional); default is a thin-shell model from `DOI:10.1016/J.NIMA.2022.166706 `__ - + * ``.dx`` (``float``, in meters) horizontal translation error + * ``.dy`` (``float``, in meters) vertical translation error + * ``.rotation`` (``float``, in degrees) rotation error in the transverse plane * ``.mapsteps`` (``integer``) number of integration steps per slice used for map and reference particle push in applied fields (default: ``1``) - * ``.nslice`` (``integer``) number of slices used for the application of space charge (default: ``1``) * ``dipedge`` for dipole edge focusing. This requires these additional parameters: * ``.psi`` (``float``, in radians) the pole face rotation angle - * ``.rc`` (``float``, in meters) the bend radius - * ``.g`` (``float``, in meters) the gap size - * ``.K2`` (``float``, dimensionless) normalized field integral for fringe field + * ``.dx`` (``float``, in meters) horizontal translation error + * ``.dy`` (``float``, in meters) vertical translation error + * ``.rotation`` (``float``, in degrees) rotation error in the transverse plane * ``constf`` for a constant focusing element. This requires these additional parameters: * ``.ds`` (``float``, in meters) the segment length - * ``.kx`` (``float``, in 1/meters) the horizontal focusing strength - * ``.ky`` (``float``, in 1/meters) the vertical focusing strength - * ``.kt`` (``float``, in 1/meters) the longitudinal focusing strength - + * ``.dx`` (``float``, in meters) horizontal translation error + * ``.dy`` (``float``, in meters) vertical translation error + * ``.rotation`` (``float``, in degrees) rotation error in the transverse plane * ``.nslice`` (``integer``) number of slices used for the application of space charge (default: ``1``) * ``rfcavity`` a radiofrequency cavity. This requires these additional parameters: * ``.ds`` (``float``, in meters) the segment length - * ``.escale`` (``float``, in 1/m) scaling factor for on-axis RF electric field - = (peak on-axis electric field Ez in MV/m) / (particle rest energy in MeV) - * ``.freq`` (``float``, in Hz) RF frequency - * ``.phase`` (``float``, in degrees) RF driven phase - * ``.cos_coefficients`` (array of ``float``) cosine coefficients in Fourier expansion of on-axis electric field Ez (optional); default is a 9-cell TESLA superconducting cavity model from `DOI:10.1103/PhysRevSTAB.3.092001 `__ - * ``.cos_coefficients`` (array of ``float``) sine coefficients in Fourier expansion of on-axis electric field Ez (optional); default is a 9-cell TESLA superconducting cavity model from `DOI:10.1103/PhysRevSTAB.3.092001 `__ - + * ``.dx`` (``float``, in meters) horizontal translation error + * ``.dy`` (``float``, in meters) vertical translation error + * ``.rotation`` (``float``, in degrees) rotation error in the transverse plane * ``.mapsteps`` (``integer``) number of integration steps per slice used for map and reference particle push in applied fields (default: ``1``) - * ``.nslice`` (``integer``) number of slices used for the application of space charge (default: ``1``) * ``buncher`` for a short RF cavity (linear) bunching element. This requires these additional parameters: * ``.V`` (``float``, dimensionless) normalized voltage drop across the cavity - = (maximum voltage drop in Volts) / (speed of light in m/s * magnetic rigidity in T-m) - * ``.k`` (``float``, in 1/meters) the RF wavenumber - = 2*pi/(RF wavelength in m) + * ``.dx`` (``float``, in meters) horizontal translation error + * ``.dy`` (``float``, in meters) vertical translation error + * ``.rotation`` (``float``, in degrees) rotation error in the transverse plane * ``shortrf`` for a short RF cavity element. This requires these additional parameters: * ``.V`` (``float``, dimensionless) normalized voltage drop across the cavity - = (maximum energy gain in MeV) / (particle rest energy in MeV) - * ``.freq`` (``float``, in Hz) the RF frequency - * ``.phase`` (``float``, in degrees) the synchronous RF phase phase = 0: maximum energy gain (on-crest) @@ -474,76 +469,80 @@ Lattice Elements phase = -90 deg: zero energy gain for bunching phase = 90 deg: zero energy gain for debunching + * ``.dx`` (``float``, in meters) horizontal translation error + * ``.dy`` (``float``, in meters) vertical translation error + * ``.rotation`` (``float``, in degrees) rotation error in the transverse plane * ``uniform_acc_chromatic`` for a region of uniform acceleration, with chromatic effects included. The Hamiltonian is expanded through second order in the transverse variables (x,px,y,py), with the exact pt dependence retained. This requires these additional parameters: * ``.ds`` (``float``, in meters) the segment length - * ``.ez`` (``float``, in inverse meters) the electric field strength - = (particle charge in C * electric field Ez in V/m) / (particle mass in kg * (speed of light in m/s)^2) - * ``.bz`` (``float``, in inverse meters) the magnetic field strength - = (particle charge in C * magnetic field Bz in T) / (particle mass in kg * speed of light in m/s) - + * ``.dx`` (``float``, in meters) horizontal translation error + * ``.dy`` (``float``, in meters) vertical translation error + * ``.rotation`` (``float``, in degrees) rotation error in the transverse plane * ``.nslice`` (``integer``) number of slices used for the application of space charge (default: ``1``) * ``multipole`` for a thin multipole element. This requires these additional parameters: * ``.multipole`` (``integer``, dimensionless) order of multipole - (m = 1) dipole, (m = 2) quadrupole, (m = 3) sextupole, etc. * ``.k_normal`` (``float``, in 1/meters^m) integrated normal multipole coefficient (MAD-X convention) - = 1/(magnetic rigidity in T-m) * (derivative of order m-1 of By with respect to x) - * ``.k_skew`` (``float``, in 1/meters^m) integrated skew multipole strength (MAD-X convention) + * ``.dx`` (``float``, in meters) horizontal translation error + * ``.dy`` (``float``, in meters) vertical translation error + * ``.rotation`` (``float``, in degrees) rotation error in the transverse plane * ``nonlinear_lens`` for a thin IOTA nonlinear lens element. This requires these additional parameters: * ``.knll`` (``float``, in meters) integrated strength of the lens segment (MAD-X convention) - = dimensionless lens strength * c parameter**2 * length / Twiss beta - * ``.cnll`` (``float``, in meters) distance of the singularities from the origin (MAD-X convention) - = c parameter * sqrt(Twiss beta) + * ``.dx`` (``float``, in meters) horizontal translation error + * ``.dy`` (``float``, in meters) vertical translation error + * ``.rotation`` (``float``, in degrees) rotation error in the transverse plane * ``prot`` for an exact pole-face rotation in the x-z plane. This requires these additional parameters: * ``.phi_in`` (``float``, in degrees) angle of the reference particle with respect to the longitudinal (z) axis in the original frame - * ``.phi_out`` (``float``, in degrees) angle of the reference particle with respect to the longitudinal (z) axis in the rotated frame * ``kicker`` for a thin transverse kicker. This requires these additional parameters: * ``.xkick`` (``float``, dimensionless OR in T-m) the horizontal kick strength - * ``.ykick`` (``float``, dimensionless OR in T-m) the vertical kick strength - * ``.units`` (``string``) specification of units: ``dimensionless`` (default, in units of the magnetic rigidity of the reference particle) or ``T-m`` + * ``.dx`` (``float``, in meters) horizontal translation error + * ``.dy`` (``float``, in meters) vertical translation error + * ``.rotation`` (``float``, in degrees) rotation error in the transverse plane * ``thin_dipole`` for a thin dipole element. This requires these additional parameters: * ``.theta`` (``float``, in degrees) dipole bend angle - * ``.rc`` (``float``, in meters) effective radius of curvature + * ``.dx`` (``float``, in meters) horizontal translation error + * ``.dy`` (``float``, in meters) vertical translation error + * ``.rotation`` (``float``, in degrees) rotation error in the transverse plane * ``aperture`` for a thin collimator element applying a transverse aperture boundary. This requires these additional parameters: * ``.xmax`` (``float``, in meters) maximum value of the horizontal coordinate - * ``.ymax`` (``float``, in meters) maximum value of the vertical coordinate - * ``.shape`` (``string``) shape of the aperture boundary: ``rectangular`` (default) or ``elliptical`` + * ``.dx`` (``float``, in meters) horizontal translation error + * ``.dy`` (``float``, in meters) vertical translation error + * ``.rotation`` (``float``, in degrees) rotation error in the transverse plane * ``beam_monitor`` a beam monitor, writing all beam particles at fixed ``s`` to openPMD files. If the same element name is used multiple times, then an output series is created with multiple outputs. diff --git a/docs/source/usage/python.rst b/docs/source/usage/python.rst index 910ac1eba..2729edcbd 100644 --- a/docs/source/usage/python.rst +++ b/docs/source/usage/python.rst @@ -463,7 +463,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, nslice=1) +.. py:class:: impactx.elements.CFbend(ds, rc, k, dx=0, dy=0, rotation=0, nslice=1) A combined function bending magnet. This is an ideal Sbend with a normal quadrupole field component. @@ -473,9 +473,12 @@ This module provides elements for the accelerator lattice. = (gradient in T/m) / (rigidity in T-m) k > 0 horizontal focusing k < 0 horizontal defocusing + :param dx: horizontal translation error in m + :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 -.. py:class:: impactx.elements.ConstF(ds, kx, ky, kt, nslice=1) +.. py:class:: impactx.elements.ConstF(ds, kx, ky, kt, dx=0, dy=0, rotation=0, nslice=1) A linear Constant Focusing element. @@ -483,6 +486,9 @@ This module provides elements for the accelerator lattice. :param kx: Focusing strength for x in 1/m. :param ky: Focusing strength for y in 1/m. :param kt: Focusing strength for t in 1/m. + :param dx: horizontal translation error in m + :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 .. py:property:: kx @@ -497,7 +503,7 @@ This module provides elements for the accelerator lattice. focusing t strength in 1/m -.. py:class:: impactx.elements.DipEdge(psi, rc, g, K2) +.. py:class:: impactx.elements.DipEdge(psi, rc, g, K2, dx=0, dy=0, rotation=0) Edge focusing associated with bend entry or exit @@ -505,6 +511,7 @@ This module provides elements for the accelerator lattice. Here we use the linear fringe field map, given to first order in g/rc (gap / radius of curvature). References: + * K. L. Brown, SLAC Report No. 75 (1982). * K. Hwang and S. Y. Lee, PRAB 18, 122401 (2015). @@ -512,31 +519,40 @@ This module provides elements for the accelerator lattice. :param rc: Radius of curvature in m :param g: Gap parameter in m :param K2: Fringe field integral (unitless) + :param dx: horizontal translation error in m + :param dy: vertical translation error in m + :param rotation: rotation error in the transverse plane [degrees] -.. py:class:: impactx.elements.Drift(ds, nslice=1) +.. py:class:: impactx.elements.Drift(ds, dx=0, dy=0, rotation=0, nslice=1) A drift. :param ds: Segment length in m :param nslice: number of slices used for the application of space charge -.. py:class:: impactx.elements.ChrDrift(ds, nslice=1) +.. py:class:: impactx.elements.ChrDrift(ds, dx=0, dy=0, rotation=0, nslice=1) 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 dependence retained. :param ds: Segment length in m + :param dx: horizontal translation error in m + :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 -.. py:class:: impactx.elements.ExactDrift(ds, nslice=1) +.. py:class:: impactx.elements.ExactDrift(ds, dx=0, dy=0, rotation=0, nslice=1) A drift using the exact nonlinear transfer map. :param ds: Segment length in m + :param dx: horizontal translation error in m + :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 -.. py:class:: impactx.elements.Kicker(xkick, ykick, units) +.. py:class:: impactx.elements.Kicker(xkick, ykick, units, dx=0, dy=0, rotation=0) A thin transverse kicker. @@ -544,29 +560,35 @@ This module provides elements for the accelerator lattice. :param ykick: vertical kick strength (dimensionless OR T-m) :param units: specification of units (``"dimensionless"`` in units of the magnetic rigidity of the reference particle or ``"T-m"``) -.. py:class:: impactx.elements.Multipole(multipole, K_normal, K_skew) +.. py:class:: impactx.elements.Multipole(multipole, K_normal, K_skew, dx=0, dy=0, rotation=0) A general thin multipole element. :param multipole: index m (m=1 dipole, m=2 quadrupole, m=3 sextupole etc.) :param K_normal: Integrated normal multipole coefficient (1/meter^m) :param K_skew: Integrated skew multipole coefficient (1/meter^m) + :param dx: horizontal translation error in m + :param dy: vertical translation error in m + :param rotation: rotation error in the transverse plane [degrees] .. py::class:: impactx.elements.None This element does nothing. -.. py:class:: impactx.elements.NonlinearLens(knll, cnll) +.. py:class:: impactx.elements.NonlinearLens(knll, cnll, dx=0, dy=0, rotation=0) Single short segment of the nonlinear magnetic insert element. A thin lens associated with a single short segment of the nonlinear magnetic insert described by V. Danilov and S. Nagaitsev, PRSTAB 13, 084002 (2010), Sect. V.A. This - element appears in MAD-X as type NLLENS. + element appears in MAD-X as type ``NLLENS``. :param knll: integrated strength of the nonlinear lens (m) :param cnll: distance of singularities from the origin (m) + :param dx: horizontal translation error in m + :param dy: vertical translation error in m + :param rotation: rotation error in the transverse plane [degrees] .. py:class:: impactx.elements.BeamMonitor(name, backend="default", encoding="g") @@ -612,7 +634,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, nslice=1) +.. py:class:: impactx.elements.Quad(ds, k, dx=0, dy=0, rotation=0, nslice=1) A Quadrupole magnet. @@ -621,9 +643,12 @@ This module provides elements for the accelerator lattice. = (gradient in T/m) / (rigidity in T-m) k > 0 horizontal focusing k < 0 horizontal defocusing + :param dx: horizontal translation error in m + :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 -.. py:class:: impactx.elements.ChrQuad(ds, k, units, nslice=1) +.. py:class:: impactx.elements.ChrQuad(ds, k, units, dx=0, dy=0, rotation=0, nslice=1) 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 @@ -636,9 +661,12 @@ This module provides elements for the accelerator lattice. k > 0 horizontal focusing k < 0 horizontal defocusing :param units: specification of units for quadrupole field strength + :param dx: horizontal translation error in m + :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 -.. py:class:: impactx.elements.RFCavity(ds, escale, freq, phase, mapsteps, nslice) +.. py:class:: impactx.elements.RFCavity(ds, escale, freq, phase, dx=0, dy=0, rotation=0, mapsteps=1, nslice=1) A radiofrequency cavity. @@ -650,47 +678,63 @@ This module provides elements for the accelerator lattice. :param cos_coefficients: array of ``float`` cosine coefficients in Fourier expansion of on-axis electric field Ez (optional); default is a 9-cell TESLA superconducting cavity model from `DOI:10.1103/PhysRevSTAB.3.092001 `__ :param cos_coefficients: array of ``float`` sine coefficients in Fourier expansion of on-axis electric field Ez (optional); default is a 9-cell TESLA superconducting cavity model from `DOI:10.1103/PhysRevSTAB.3.092001 `__ + :param dx: horizontal translation error in m + :param dy: vertical translation error in m + :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 -.. py:class:: impactx.elements.Sbend(ds, rc, nslice=1) +.. py:class:: impactx.elements.Sbend(ds, rc, dx=0, dy=0, rotation=0, nslice=1) An ideal sector bend. :param ds: Segment length in m. :param rc: Radius of curvature in m. + :param dx: horizontal translation error in m + :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 -.. py:class:: impactx.elements.ExactSbend(ds, phi, B, nslice=1) +.. py:class:: impactx.elements.ExactSbend(ds, phi, B, dx=0, dy=0, rotation=0, nslice=1) 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. -References: + References: + * D. L. Bruhwiler et al, in Proc. of EPAC 98, pp. 1171-1173 (1998). * E. Forest et al, Part. Accel. 45, pp. 65-94 (1994). :param ds: Segment length in m. :param phi: Bend angle in degrees. :param B: Magnetic field in Tesla; when B = 0 (default), 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. + :param dx: horizontal translation error in m + :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 -.. py:class:: impactx.elements.Buncher(V, k) +.. py:class:: impactx.elements.Buncher(V, k, dx=0, dy=0, rotation=0) A short RF cavity element at zero crossing for bunching (MaryLie model). :param V: Normalized RF voltage drop V = Emax*L/(c*Brho) :param k: Wavenumber of RF in 1/m + :param dx: horizontal translation error in m + :param dy: vertical translation error in m + :param rotation: rotation error in the transverse plane [degrees] -.. py:class:: impactx.elements.ShortRF(V, freq, phase) +.. py:class:: impactx.elements.ShortRF(V, freq, phase, dx=0, dy=0, rotation=0) A short RF cavity element (MAD-X model). :param V: Normalized RF voltage V = maximum energy gain/(m*c^2) :param freq: RF frequency in Hz :param phase: RF synchronous phase in degrees (phase = 0 corresponds to maximum energy gain, phase = -90 corresponds go zero energy gain for bunching) + :param dx: horizontal translation error in m + :param dy: vertical translation error in m + :param rotation: rotation error in the transverse plane [degrees] -.. py:class:: impactx.elements.ChrUniformAcc(ds, k, nslice=1) +.. py:class:: impactx.elements.ChrUniformAcc(ds, k, dx=0, dy=0, rotation=0, nslice=1) A region of constant Ez and Bz for uniform acceleration, with chromatic effects included. The Hamiltonian is expanded through second order in the transverse variables (x,px,y,py), @@ -701,10 +745,12 @@ References: = (particle charge in C * field Ez in V/m) / (particle mass in kg * (speed of light in m/s)^2) :param bz: Magnetic field strength in m^(-1) = (particle charge in C * field Bz in T) / (particle mass in kg * speed of light in m/s) - + :param dx: horizontal translation error in m + :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 -.. py:class:: impactx.elements.SoftSolenoid(ds, bscale, cos_coefficients, sin_coefficients, nslice=1) +.. py:class:: impactx.elements.SoftSolenoid(ds, bscale, cos_coefficients, sin_coefficients, dx=0, dy=0, rotation=0, mapsteps=1, nslice=1) A soft-edge solenoid. @@ -714,15 +760,21 @@ References: (optional); default is a thin-shell model from `DOI:10.1016/J.NIMA.2022.166706 `__ :param sin_coefficients: array of ``float`` sine coefficients in Fourier expansion of on-axis magnetic field Bz (optional); default is a thin-shell model from `DOI:10.1016/J.NIMA.2022.166706 `__ + :param dx: horizontal translation error in m + :param dy: vertical translation error in m + :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 -.. py:class:: impactx.elements.Sol(ds, ks, nslice=1) +.. py:class:: impactx.elements.Sol(ds, ks, dx=0, dy=0, rotation=0, nslice=1) An ideal hard-edge Solenoid magnet. :param ds: Segment length in m. :param ks: Solenoid strength in m^(-1) (MADX convention) in (magnetic field Bz in T) / (rigidity in T-m) + :param dx: horizontal translation error in m + :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 .. py:class:: impactx.elements.PRot(phi_in, phi_out) @@ -731,16 +783,22 @@ References: :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 dx: horizontal translation error in m + :param dy: vertical translation error in m + :param rotation: rotation error in the transverse plane [degrees] -.. py:class:: impactx.elements.Aperture(xmax, ymax, shape="rectangular") +.. py:class:: impactx.elements.Aperture(xmax, ymax, shape="rectangular", dx=0, dy=0, rotation=0) A thin collimator element, applying a transverse aperture boundary. :param xmax: maximum allowed value of the horizontal coordinate (meter) :param ymax: maximum allowed value of the vertical coordinate (meter) :param shape: aperture boundary shape: ``"rectangular"`` (default) or ``"elliptical"`` + :param dx: horizontal translation error in m + :param dy: vertical translation error in m + :param rotation: rotation error in the transverse plane [degrees] -.. py:class:: impactx.elements.SoftQuadrupole(ds, gscale, cos_coefficients, sin_coefficients, nslice=1) +.. py:class:: impactx.elements.SoftQuadrupole(ds, gscale, cos_coefficients, sin_coefficients, dx=0, dy=0, rotation=0, mapsteps=1, nslice=1) A soft-edge quadrupole. @@ -750,19 +808,27 @@ References: (optional); default is a tanh fringe field model based on ``__ :param sin_coefficients: array of ``float`` sine coefficients in Fourier expansion of on-axis field gradient (optional); default is a tanh fringe field model based on ``__ + :param dx: horizontal translation error in m + :param dy: vertical translation error in m + :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 -.. py:class:: impactx.elements.ThinDipole(theta, rc) +.. py:class:: impactx.elements.ThinDipole(theta, rc, dx=0, dy=0, rotation=0) A general thin dipole element. :param theta: Bend angle (degrees) :param rc: Effective curvature radius (meters) + :param dx: horizontal translation error in m + :param dy: vertical translation error in m + :param rotation: rotation error in the transverse plane [degrees] + + Reference: -Reference: * G. Ripken and F. Schmidt, Thin-Lens Formalism for Tracking, CERN/SL/95-12 (AP), 1995. + Coordinate Transformation ------------------------- @@ -773,8 +839,7 @@ Coordinate Transformation :param to_fixed_t: :param to_fixed_s: -Function -.. py:method:: impactx.coordinate_transformation(pc, direction) +.. py:function:: impactx.coordinate_transformation(pc, direction) Function to transform the coordinates of the particles in a particle container either to fixed :math:`t` or to fixed :math:`s`. diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 3907f03fe..ef9fbe0bd 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -780,3 +780,21 @@ add_impactx_test(apochromat.py examples/apochromatic/analysis_apochromatic.py OFF # no plot script yet ) + +# Misalignment Quad ########################################################### +# +# w/o space charge +add_impactx_test(alignment + examples/alignment/input_alignment.in + ON # ImpactX MPI-parallel + OFF # ImpactX Python interface + examples/alignment/analysis_alignment.py + OFF # no plot script yet +) +add_impactx_test(alignment.py + examples/alignment/run_alignment.py + OFF # ImpactX MPI-parallel + ON # ImpactX Python interface + examples/alignment/analysis_alignment.py + OFF # no plot script yet +) diff --git a/examples/alignment/README.rst b/examples/alignment/README.rst new file mode 100644 index 000000000..a8be474fc --- /dev/null +++ b/examples/alignment/README.rst @@ -0,0 +1,49 @@ +.. _examples-alignment: + +Quadrupole with Alignment Errors +================================ + +A 2 GeV proton beam propagates through a single quadrupole with 3 mm horizontal misalignment and 30 degree rotation error. + +The first and second moments of the particle distribution before and after the quadrupole should coincide with +analytical predictions, to within the level expected due to noise due to statistical sampling. + +In this test, the initial and final values of :math:`\mu_x`, :math:`\mu_y`, :math:`\sigma_x`, :math:`\sigma_y`, +:math:`\sigma_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_alignment.py`` or +* ImpactX **executable** using an input file: ``impactx input_alignment.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_alignment.py + :language: python3 + :caption: You can copy this file from ``examples/alignment/run_alignment.py``. + + .. tab-item:: Executable: Input File + + .. literalinclude:: input_alignment.in + :language: ini + :caption: You can copy this file from ``examples/alignment/input_alignment.in``. + + +Analyze +------- + +We run the following script to analyze correctness: + +.. dropdown:: Script ``analysis_alignment.py`` + + .. literalinclude:: analysis_alignment.py + :language: python3 + :caption: You can copy this file from ``examples/alignment/analysis_alignment.py``. diff --git a/examples/alignment/analysis_alignment.py b/examples/alignment/analysis_alignment.py new file mode 100755 index 000000000..d84e95fd8 --- /dev/null +++ b/examples/alignment/analysis_alignment.py @@ -0,0 +1,137 @@ +#!/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, tmean + + +def get_moments(beam): + """Calculate standard deviations of beam position & momenta + and emittance values + + Returns + ------- + meanx, meany, sigx, sigy, sigt, emittance_x, emittance_y, emittance_t + """ + meanx = tmean(beam["position_x"]) + meany = tmean(beam["position_y"]) + 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 (meanx, meany, 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 = 100000 +assert num_particles == len(initial) +assert num_particles == len(final) + +print("Initial Beam:") +meanx, meany, sigx, sigy, sigt, emittance_x, emittance_y, emittance_t = get_moments( + initial +) +print(f" meanx={meanx:e} meany={meany:e}") +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 = 3.0 * num_particles ** (-0.5) * sigx +print(f" atol~={atol}") + +assert np.allclose( + [meanx, meany], + [ + 0.0, + 0.0, + ], + atol=atol, +) + +atol = 0.0 # ignored +rtol = 1.8 * 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.160982600086e-3, + 1.160982600086e-3, + 1.0e-3, + 6.73940299e-7, + 6.73940299e-7, + 2.0e-6, + ], + rtol=rtol, + atol=atol, +) + + +print("") +print("Final Beam:") +meanx, meany, sigx, sigy, sigt, emittance_x, emittance_y, emittance_t = get_moments( + final +) +print(f" meanx={meanx:e} meany={meany:e}") +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 = 3.0 * num_particles ** (-0.5) * sigx +print(f" atol~={atol}") + +assert np.allclose( + [meanx, meany], + [ + 1.79719761842e-4, + 3.24815908981e-4, + ], + atol=atol, +) + +atol = 0.0 # ignored +rtol = 3.0 * 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.2372883901369e-3, + 1.3772750218080e-3, + 1.027364e-03, + 7.39388142e-7, + 7.39388142e-7, + 2.0e-6, + ], + rtol=rtol, + atol=atol, +) diff --git a/examples/alignment/input_alignment.in b/examples/alignment/input_alignment.in new file mode 100644 index 000000000..b98a42c0e --- /dev/null +++ b/examples/alignment/input_alignment.in @@ -0,0 +1,47 @@ +############################################################################### +# Particle Beam(s) +############################################################################### +beam.npart = 100000 +beam.units = static +beam.kin_energy = 2.0e3 +beam.charge = 1.0e-9 +beam.particle = proton +beam.distribution = waterbag +beam.sigmaX = 1.16098260008648811e-3 +beam.sigmaY = 1.16098260008648811e-3 +beam.sigmaT = 1.0e-3 +beam.sigmaPx = 0.580491300043e-3 +beam.sigmaPy = 0.580491300043e-3 +beam.sigmaPt = 2.0e-3 +beam.muxpx = 0.0 +beam.muypy = 0.0 +beam.mutpt = 0.0 + + +############################################################################### +# Beamline: lattice elements and segments +############################################################################### +lattice.elements = monitor quad_err monitor +lattice.nslice = 1 + +monitor.type = beam_monitor +monitor.backend = h5 + +quad_err.type = quad +quad_err.ds = 1.0 +quad_err.k = 0.25 +quad_err.dx = 0.003 +quad_err.rotation = 30.0 + + +############################################################################### +# Algorithms +############################################################################### +algo.particle_shape = 2 +algo.space_charge = false + + +############################################################################### +# Diagnostics +############################################################################### +diag.slice_step_diagnostics = true diff --git a/examples/alignment/run_alignment.py b/examples/alignment/run_alignment.py new file mode 100755 index 000000000..88524dcf8 --- /dev/null +++ b/examples/alignment/run_alignment.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# +# Copyright 2022-2023 ImpactX contributors +# Authors: Axel Huebl, Chad Mitchell +# License: BSD-3-Clause-LBNL +# +# -*- coding: utf-8 -*- + +import amrex.space3d as amr +from impactx import ImpactX, RefPart, 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 +kin_energy_MeV = 2.0e3 # reference energy +bunch_charge_C = 1.0e-9 # used with space charge +npart = 100000 # 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.Waterbag( + sigmaX=1.16098260008648811e-3, + sigmaY=1.16098260008648811e-3, + sigmaT=1.0e-3, + sigmaPx=0.580491300043e-3, + sigmaPy=0.580491300043e-3, + sigmaPt=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) +ns = 1 # number of slices per ds in the element +lattice = [ + monitor, + elements.Quad(ds=1.0, k=0.25, dx=0.003, dy=0.0, rotation=30.0, nslice=ns), + monitor, +] +sim.lattice.extend(lattice) + +# run simulation +sim.evolve() + +# clean shutdown +del sim +amr.finalize() diff --git a/src/initialization/InitElement.cpp b/src/initialization/InitElement.cpp index b5f5de705..05439f814 100644 --- a/src/initialization/InitElement.cpp +++ b/src/initialization/InitElement.cpp @@ -17,7 +17,9 @@ #include #include +#include #include +#include #include @@ -47,6 +49,50 @@ namespace detail } return exist; } + + /** Read the Thick element parameters ds and nslice + * + * @param pp_element the element being read + * @param nslice_default the default number of slices to use if not specified + * @return total element length (ds) and number of slices through it (nslice) + */ + std::pair + query_ds (amrex::ParmParse& pp_element, int nslice_default) + { + amrex::ParticleReal ds; + int nslice = nslice_default; + pp_element.get("ds", ds); + pp_element.queryAdd("nslice", nslice); + + AMREX_ALWAYS_ASSERT_WITH_MESSAGE(nslice > 0, + pp_element.getPrefix() + ".nslice must be > 0."); + + return {ds, nslice}; + } + + /** Read the Alignment parameters dx, dy and rotation from inputs + * + * @param pp_element the element being read + * @return key-value pairs for dx, dy and rotation_degree + */ + std::map + query_alignment (amrex::ParmParse& pp_element) + { + amrex::ParticleReal dx = 0; + amrex::ParticleReal dy = 0; + amrex::ParticleReal rotation_degree = 0; + pp_element.query("dx", dx); + pp_element.query("dy", dy); + pp_element.query("rotation", rotation_degree); + + std::map values = { + {"dx", dx}, + {"dy", dy}, + {"rotation_degree", rotation_degree} + }; + + return values; + } } // namespace detail /** Read a lattice element @@ -69,175 +115,226 @@ namespace detail pp_element.get("type", element_type); // Initialize the corresponding element according to its type - if (element_type == "quad") { - amrex::Real ds, k; - int nslice = nslice_default; - pp_element.get("ds", ds); + if (element_type == "quad") + { + auto const [ds, nslice] = detail::query_ds(pp_element, nslice_default); + auto a = detail::query_alignment(pp_element); + + amrex::ParticleReal k; pp_element.get("k", k); - pp_element.queryAdd("nslice", nslice); - m_lattice.emplace_back( Quad(ds, k, nslice) ); - } else if (element_type == "drift") { - amrex::Real ds; - int nslice = nslice_default; - pp_element.get("ds", ds); - pp_element.queryAdd("nslice", nslice); - m_lattice.emplace_back( Drift(ds, nslice) ); - } else if (element_type == "sbend") { - amrex::Real ds, rc; - int nslice = nslice_default; - pp_element.get("ds", ds); + + m_lattice.emplace_back( Quad(ds, k, a["dx"], a["dy"], a["rotation_degree"], nslice) ); + } 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) ); + } else if (element_type == "sbend") + { + auto const [ds, nslice] = detail::query_ds(pp_element, nslice_default); + auto a = detail::query_alignment(pp_element); + + amrex::ParticleReal rc; pp_element.get("rc", rc); - pp_element.queryAdd("nslice", nslice); - m_lattice.emplace_back( Sbend(ds, rc, nslice) ); - } else if (element_type == "cfbend") { - amrex::Real ds, rc, k; - int nslice = nslice_default; - pp_element.get("ds", ds); + + m_lattice.emplace_back( Sbend(ds, rc, a["dx"], a["dy"], a["rotation_degree"], nslice) ); + } else if (element_type == "cfbend") + { + auto const [ds, nslice] = detail::query_ds(pp_element, nslice_default); + auto a = detail::query_alignment(pp_element); + + amrex::ParticleReal rc, k; pp_element.get("rc", rc); pp_element.get("k", k); - pp_element.queryAdd("nslice", nslice); - m_lattice.emplace_back( CFbend(ds, rc, k, nslice) ); - } else if (element_type == "dipedge") { - amrex::Real psi, rc, g, K2; + + m_lattice.emplace_back( CFbend(ds, rc, k, a["dx"], a["dy"], a["rotation_degree"], nslice) ); + } else if (element_type == "dipedge") + { + auto a = detail::query_alignment(pp_element); + + amrex::ParticleReal psi, rc, g, K2; pp_element.get("psi", psi); pp_element.get("rc", rc); pp_element.get("g", g); pp_element.get("K2", K2); - m_lattice.emplace_back( DipEdge(psi, rc, g, K2) ); - } else if (element_type == "constf") { - amrex::Real ds, kx, ky, kt; - int nslice = nslice_default; - pp_element.get("ds", ds); + + m_lattice.emplace_back( DipEdge(psi, rc, g, K2, a["dx"], a["dy"], a["rotation_degree"]) ); + } else if (element_type == "constf") + { + auto const [ds, nslice] = detail::query_ds(pp_element, nslice_default); + auto a = detail::query_alignment(pp_element); + + amrex::Real kx, ky, kt; pp_element.get("kx", kx); pp_element.get("ky", ky); pp_element.get("kt", kt); - pp_element.queryAdd("nslice", nslice); - m_lattice.emplace_back( ConstF(ds, kx, ky, kt, nslice) ); - } else if (element_type == "buncher") { - amrex::Real V, k; + + m_lattice.emplace_back( ConstF(ds, kx, ky, kt, a["dx"], a["dy"], a["rotation_degree"], nslice) ); + } else if (element_type == "buncher") + { + auto a = detail::query_alignment(pp_element); + + amrex::ParticleReal V, k; pp_element.get("V", V); pp_element.get("k", k); - m_lattice.emplace_back( Buncher(V, k) ); - } else if (element_type == "shortrf") { - amrex::Real V, freq; - amrex::Real phase = -90.0; + + m_lattice.emplace_back( Buncher(V, k, a["dx"], a["dy"], a["rotation_degree"]) ); + } else if (element_type == "shortrf") + { + auto a = detail::query_alignment(pp_element); + + amrex::ParticleReal V, freq; + amrex::ParticleReal phase = -90.0; pp_element.get("V", V); pp_element.get("freq", freq); pp_element.queryAdd("phase", phase); - m_lattice.emplace_back( ShortRF(V, freq, phase) ); - } else if (element_type == "multipole") { + + m_lattice.emplace_back( ShortRF(V, freq, phase, a["dx"], a["dy"], a["rotation_degree"]) ); + } else if (element_type == "multipole") + { + auto a = detail::query_alignment(pp_element); + int m; - amrex::Real k_normal, k_skew; + amrex::ParticleReal k_normal, k_skew; pp_element.get("multipole", m); pp_element.get("k_normal", k_normal); pp_element.get("k_skew", k_skew); - m_lattice.emplace_back( Multipole(m, k_normal, k_skew) ); - } else if (element_type == "nonlinear_lens") { - amrex::Real knll, cnll; + + m_lattice.emplace_back( Multipole(m, k_normal, k_skew, a["dx"], a["dy"], a["rotation_degree"]) ); + } else if (element_type == "nonlinear_lens") + { + auto a = detail::query_alignment(pp_element); + + amrex::ParticleReal knll, cnll; pp_element.get("knll", knll); pp_element.get("cnll", cnll); - m_lattice.emplace_back( NonlinearLens(knll, cnll) ); - } else if (element_type == "rfcavity") { - amrex::Real ds, escale, freq, phase; - int nslice = nslice_default; + + m_lattice.emplace_back( NonlinearLens(knll, cnll, a["dx"], a["dy"], a["rotation_degree"]) ); + } else if (element_type == "rfcavity") + { + auto const [ds, nslice] = detail::query_ds(pp_element, nslice_default); + auto a = detail::query_alignment(pp_element); + + amrex::ParticleReal escale, freq, phase; int mapsteps = mapsteps_default; RF_field_data const ez; std::vector cos_coef = ez.default_cos_coef; std::vector sin_coef = ez.default_sin_coef; - pp_element.get("ds", ds); pp_element.get("escale", escale); pp_element.get("freq", freq); pp_element.get("phase", phase); pp_element.queryAdd("mapsteps", mapsteps); - pp_element.queryAdd("nslice", nslice); 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, mapsteps, nslice) ); - } else if (element_type == "solenoid") { - amrex::Real ds, ks; - int nslice = nslice_default; - pp_element.get("ds", ds); + + m_lattice.emplace_back( RFCavity(ds, escale, freq, phase, cos_coef, sin_coef, a["dx"], a["dy"], a["rotation_degree"], mapsteps, nslice) ); + } else if (element_type == "solenoid") + { + auto const [ds, nslice] = detail::query_ds(pp_element, nslice_default); + auto a = detail::query_alignment(pp_element); + + amrex::ParticleReal ks; pp_element.get("ks", ks); - pp_element.queryAdd("nslice", nslice); - m_lattice.emplace_back( Sol(ds, ks, nslice) ); - } else if (element_type == "prot") { + + m_lattice.emplace_back( Sol(ds, ks, a["dx"], a["dy"], a["rotation_degree"], nslice) ); + } 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) ); - } else if (element_type == "solenoid_softedge") { - amrex::Real ds, bscale; - int nslice = nslice_default; + } else if (element_type == "solenoid_softedge") + { + auto const [ds, nslice] = detail::query_ds(pp_element, nslice_default); + auto a = detail::query_alignment(pp_element); + + amrex::ParticleReal bscale; int mapsteps = mapsteps_default; Sol_field_data const bz; std::vector cos_coef = bz.default_cos_coef; std::vector sin_coef = bz.default_sin_coef; - pp_element.get("ds", ds); pp_element.get("bscale", bscale); pp_element.queryAdd("mapsteps", mapsteps); - pp_element.queryAdd("nslice", nslice); 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, mapsteps, nslice) ); - } else if (element_type == "quadrupole_softedge") { - amrex::Real ds, gscale; - int nslice = nslice_default; + + m_lattice.emplace_back( SoftSolenoid(ds, bscale, cos_coef, sin_coef, a["dx"], a["dy"], a["rotation_degree"], mapsteps, nslice) ); + } else if (element_type == "quadrupole_softedge") + { + auto const [ds, nslice] = detail::query_ds(pp_element, nslice_default); + auto a = detail::query_alignment(pp_element); + + amrex::ParticleReal gscale; int mapsteps = mapsteps_default; Quad_field_data const gz; std::vector cos_coef = gz.default_cos_coef; std::vector sin_coef = gz.default_sin_coef; - pp_element.get("ds", ds); pp_element.get("gscale", gscale); pp_element.queryAdd("mapsteps", mapsteps); - pp_element.queryAdd("nslice", nslice); 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, mapsteps, nslice) ); - } else if (element_type == "drift_chromatic") { - amrex::Real ds; - int nslice = nslice_default; - pp_element.get("ds", ds); - pp_element.queryAdd("nslice", nslice); - m_lattice.emplace_back( ChrDrift(ds, nslice) ); - } else if (element_type == "quad_chromatic") { - amrex::Real ds, k; + + m_lattice.emplace_back( SoftQuadrupole(ds, gscale, cos_coef, sin_coef, a["dx"], a["dy"], a["rotation_degree"], mapsteps, nslice) ); + } 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) ); + } else if (element_type == "quad_chromatic") + { + auto a = detail::query_alignment(pp_element); + auto const [ds, nslice] = detail::query_ds(pp_element, nslice_default); + + amrex::ParticleReal k; int units = 0; - int nslice = nslice_default; - pp_element.get("ds", ds); pp_element.get("k", k); pp_element.queryAdd("units", units); - pp_element.queryAdd("nslice", nslice); - m_lattice.emplace_back( ChrQuad(ds, k, units, nslice) ); - } else if (element_type == "drift_exact") { - amrex::Real ds; - int nslice = nslice_default; - pp_element.get("ds", ds); - pp_element.queryAdd("nslice", nslice); - m_lattice.emplace_back( ExactDrift(ds, nslice) ); - } else if (element_type == "sbend_exact") { - amrex::Real ds, phi; - amrex::Real B = 0.0; - int nslice = nslice_default; - pp_element.get("ds", ds); + + m_lattice.emplace_back( ChrQuad(ds, k, units, a["dx"], a["dy"], a["rotation_degree"], nslice) ); + } 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) ); + } else if (element_type == "sbend_exact") + { + auto const [ds, nslice] = detail::query_ds(pp_element, nslice_default); + auto a = detail::query_alignment(pp_element); + + amrex::ParticleReal phi; + amrex::ParticleReal B = 0.0; pp_element.get("phi", phi); pp_element.queryAdd("B", B); - pp_element.queryAdd("nslice", nslice); - m_lattice.emplace_back( ExactSbend(ds, phi, B, nslice) ); - } else if (element_type == "uniform_acc_chromatic") { - amrex::Real ds, ez, bz; - int nslice = nslice_default; - pp_element.get("ds", ds); + + m_lattice.emplace_back( ExactSbend(ds, phi, B, a["dx"], a["dy"], a["rotation_degree"], nslice) ); + } else if (element_type == "uniform_acc_chromatic") + { + auto const [ds, nslice] = detail::query_ds(pp_element, nslice_default); + auto a = detail::query_alignment(pp_element); + + amrex::ParticleReal ez, bz; pp_element.get("ez", ez); pp_element.get("bz", bz); - pp_element.queryAdd("nslice", nslice); - m_lattice.emplace_back( ChrAcc(ds, ez, bz, nslice) ); - } else if (element_type == "thin_dipole") { - amrex::Real theta, rc; + + m_lattice.emplace_back( ChrAcc(ds, ez, bz, a["dx"], a["dy"], a["rotation_degree"], nslice) ); + } else if (element_type == "thin_dipole") + { + auto a = detail::query_alignment(pp_element); + + amrex::ParticleReal theta, rc; pp_element.get("theta", theta); pp_element.get("rc", rc); - m_lattice.emplace_back( ThinDipole(theta, rc) ); - } else if (element_type == "kicker") { - amrex::Real xkick, ykick; + + m_lattice.emplace_back( ThinDipole(theta, rc, a["dx"], a["dy"], a["rotation_degree"]) ); + } else if (element_type == "kicker") + { + auto a = detail::query_alignment(pp_element); + + amrex::ParticleReal xkick, ykick; std::string units_str = "dimensionless"; pp_element.get("xkick", xkick); pp_element.get("ykick", ykick); @@ -247,8 +344,12 @@ namespace detail Kicker::UnitSystem const units = units_str == "dimensionless" ? Kicker::UnitSystem::dimensionless : Kicker::UnitSystem::Tm; - m_lattice.emplace_back( Kicker(xkick, ykick, units) ); - } else if (element_type == "aperture") { + + m_lattice.emplace_back( Kicker(xkick, ykick, units, a["dx"], a["dy"], a["rotation_degree"]) ); + } else if (element_type == "aperture") + { + auto a = detail::query_alignment(pp_element); + amrex::Real xmax, ymax; std::string shape_str = "rectangular"; pp_element.get("xmax", xmax); @@ -259,16 +360,20 @@ namespace detail Aperture::Shape shape = shape_str == "rectangular" ? Aperture::Shape::rectangular : Aperture::Shape::elliptical; - m_lattice.emplace_back( Aperture(xmax, ymax, shape) ); - } else if (element_type == "beam_monitor") { + + m_lattice.emplace_back( Aperture(xmax, ymax, shape, a["dx"], a["dy"], a["rotation_degree"]) ); + } else if (element_type == "beam_monitor") + { std::string openpmd_name = element_name; pp_element.queryAdd("name", openpmd_name); std::string openpmd_backend = "default"; pp_element.queryAdd("backend", openpmd_backend); std::string openpmd_encoding{"g"}; pp_element.queryAdd("encoding", openpmd_encoding); + m_lattice.emplace_back(diagnostics::BeamMonitor(openpmd_name, openpmd_backend, openpmd_encoding)); - } else if (element_type == "line") { + } else if (element_type == "line") + { // Parse the lattice elements for the sub-lattice in the line amrex::ParmParse pp_sub_lattice(element_name); std::vector sub_lattice_elements; diff --git a/src/particles/elements/Aperture.H b/src/particles/elements/Aperture.H index 3325b7718..6da0ee037 100644 --- a/src/particles/elements/Aperture.H +++ b/src/particles/elements/Aperture.H @@ -12,6 +12,7 @@ #include "particles/ImpactXParticleContainer.H" +#include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thin.H" #include "mixin/nofinalize.H" @@ -27,6 +28,7 @@ namespace impactx struct Aperture : public elements::BeamOptic, public elements::Thin, + public elements::Alignment, public elements::NoFinalize { static constexpr auto name = "Aperture"; @@ -44,11 +46,20 @@ namespace impactx * @param shape aperture shape * @param xmax maximum value of horizontal coordinate (m) * @param ymax maximum value of vertical coordinate (m) + * @param dx horizontal translation error in m + * @param dy vertical translation error in m + * @param rotation_degree rotation error in the transverse plane [degrees] */ - Aperture (amrex::ParticleReal xmax, - amrex::ParticleReal ymax, - Shape shape) - : m_shape(shape), m_xmax(xmax), m_ymax(ymax) + Aperture ( + amrex::ParticleReal xmax, + amrex::ParticleReal ymax, + Shape shape, + amrex::ParticleReal dx = 0, + amrex::ParticleReal dy = 0, + amrex::ParticleReal rotation_degree = 0 + ) + : Alignment(dx, dy, rotation_degree), + m_shape(shape), m_xmax(xmax), m_ymax(ymax) { } @@ -74,6 +85,9 @@ namespace impactx { using namespace amrex::literals; // for _rt and _prt + // shift due to alignment errors of the element + shift_in(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); + // access AoS data such as positions and cpu/id amrex::ParticleReal const x = p.pos(RealAoS::x); amrex::ParticleReal const y = p.pos(RealAoS::y); @@ -98,6 +112,9 @@ namespace impactx } break; } + + // undo shift due to alignment errors of the element + shift_out(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); } /** This pushes the reference particle. */ diff --git a/src/particles/elements/Buncher.H b/src/particles/elements/Buncher.H index 39e59ffd6..b8dda736b 100644 --- a/src/particles/elements/Buncher.H +++ b/src/particles/elements/Buncher.H @@ -11,6 +11,7 @@ #define IMPACTX_BUNCHER_H #include "particles/ImpactXParticleContainer.H" +#include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thin.H" #include "mixin/nofinalize.H" @@ -26,6 +27,7 @@ namespace impactx struct Buncher : public elements::BeamOptic, public elements::Thin, + public elements::Alignment, public elements::NoFinalize { static constexpr auto name = "Buncher"; @@ -35,9 +37,19 @@ namespace impactx * * @param V Normalized RF voltage drop V = Emax*L/(c*Brho) * @param k Wavenumber of RF in 1/m + * @param dx horizontal translation error in m + * @param dy vertical translation error in m + * @param rotation_degree rotation error in the transverse plane [degrees] */ - Buncher( amrex::ParticleReal const V, amrex::ParticleReal const k ) - : m_V(V), m_k(k) + Buncher ( + amrex::ParticleReal V, + amrex::ParticleReal k, + amrex::ParticleReal dx = 0, + amrex::ParticleReal dy = 0, + amrex::ParticleReal rotation_degree = 0 + ) + : Alignment(dx, dy, rotation_degree), + m_V(V), m_k(k) { } @@ -63,6 +75,9 @@ namespace impactx using namespace amrex::literals; // for _rt and _prt + // shift due to alignment errors of the element + shift_in(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); + // access AoS data such as positions and cpu/id amrex::ParticleReal const x = p.pos(RealAoS::x); amrex::ParticleReal const y = p.pos(RealAoS::y); @@ -92,6 +107,8 @@ namespace impactx py = pyout; pt = ptout; + // undo shift due to alignment errors of the element + shift_out(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); } /** This pushes the reference particle. */ diff --git a/src/particles/elements/CFbend.H b/src/particles/elements/CFbend.H index 2a4e24f48..b925a5ab6 100644 --- a/src/particles/elements/CFbend.H +++ b/src/particles/elements/CFbend.H @@ -11,6 +11,7 @@ #define IMPACTX_CFBEND_H #include "particles/ImpactXParticleContainer.H" +#include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thick.H" #include "mixin/nofinalize.H" @@ -27,6 +28,7 @@ namespace impactx struct CFbend : public elements::BeamOptic, public elements::Thick, + public elements::Alignment, public elements::NoFinalize { static constexpr auto name = "CFbend"; @@ -41,14 +43,23 @@ namespace impactx * = (gradient in T/m) / (rigidity in T-m) * k > 0 horizontal focusing * k < 0 horizontal defocusing + * @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 nslice number of slices used for the application of space charge */ - CFbend( - amrex::ParticleReal const ds, - amrex::ParticleReal const rc, - amrex::ParticleReal const k, - int const nslice) - : Thick(ds, nslice), m_rc(rc), m_k(k) + CFbend ( + amrex::ParticleReal ds, + amrex::ParticleReal rc, + amrex::ParticleReal k, + amrex::ParticleReal dx = 0, + amrex::ParticleReal dy = 0, + amrex::ParticleReal rotation_degree = 0, + int nslice = 1 + ) + : Thick(ds, nslice), + Alignment(dx, dy, rotation_degree), + m_rc(rc), m_k(k) { } @@ -73,6 +84,9 @@ namespace impactx { using namespace amrex::literals; // for _rt and _prt + // shift due to alignment errors of the element + shift_in(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); + // access AoS data such as positions and cpu/id amrex::ParticleReal const x = p.pos(RealAoS::x); amrex::ParticleReal const y = p.pos(RealAoS::y); @@ -150,6 +164,9 @@ namespace impactx px = pxout; py = pyout; pt = ptout; + + // undo shift due to alignment errors of the element + shift_out(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); } /** This pushes the reference particle. diff --git a/src/particles/elements/ChrDrift.H b/src/particles/elements/ChrDrift.H index 389b2d189..92f940ce3 100644 --- a/src/particles/elements/ChrDrift.H +++ b/src/particles/elements/ChrDrift.H @@ -11,6 +11,7 @@ #define IMPACTX_CHRDRIFT_H #include "particles/ImpactXParticleContainer.H" +#include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thick.H" #include "mixin/nofinalize.H" @@ -26,6 +27,7 @@ namespace impactx struct ChrDrift : public elements::BeamOptic, public elements::Thick, + public elements::Alignment, public elements::NoFinalize { static constexpr auto name = "ChrDrift"; @@ -37,10 +39,20 @@ namespace impactx * transverse variables (x,px,y,py), with the exact pt dependence retained. * * @param ds Segment length in m + * @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 nslice number of slices used for the application of space charge */ - ChrDrift( amrex::ParticleReal const ds, int const nslice ) - : Thick(ds, nslice) + ChrDrift ( + amrex::ParticleReal ds, + amrex::ParticleReal dx = 0, + amrex::ParticleReal dy = 0, + amrex::ParticleReal rotation_degree = 0, + int nslice = 1 + ) + : Thick(ds, nslice), + Alignment(dx, dy, rotation_degree) { } @@ -65,6 +77,9 @@ namespace impactx using namespace amrex::literals; // for _rt and _prt + // shift due to alignment errors of the element + shift_in(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); + // access AoS data such as positions and cpu/id amrex::ParticleReal const x = p.pos(RealAoS::x); amrex::ParticleReal const y = p.pos(RealAoS::y); @@ -106,6 +121,8 @@ namespace impactx py = pyout; pt = ptout; + // undo shift due to alignment errors of the element + shift_out(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); } /** This pushes the reference particle. diff --git a/src/particles/elements/ChrQuad.H b/src/particles/elements/ChrQuad.H index 81ad83b28..ba5ed497b 100644 --- a/src/particles/elements/ChrQuad.H +++ b/src/particles/elements/ChrQuad.H @@ -11,6 +11,7 @@ #define IMPACTX_CHRQUAD_H #include "particles/ImpactXParticleContainer.H" +#include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thick.H" #include "mixin/nofinalize.H" @@ -27,6 +28,7 @@ namespace impactx struct ChrQuad : public elements::BeamOptic, public elements::Thick, + public elements::Alignment, public elements::NoFinalize { static constexpr auto name = "ChrQuad"; @@ -46,11 +48,23 @@ namespace impactx * @param unit Unit specification * unit = 0 MADX convention (default) * unit = 1 MaryLie convention + * @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 nslice number of slices used for the application of space charge */ - ChrQuad( amrex::ParticleReal const ds, amrex::ParticleReal const k, - int const unit, int const nslice ) - : Thick(ds, nslice), m_k(k), m_unit(unit) + ChrQuad ( + amrex::ParticleReal ds, + amrex::ParticleReal k, + int unit, + amrex::ParticleReal dx = 0, + amrex::ParticleReal dy = 0, + amrex::ParticleReal rotation_degree = 0, + int nslice = 1 + ) + : Thick(ds, nslice), + Alignment(dx, dy, rotation_degree), + m_k(k), m_unit(unit) { } @@ -75,6 +89,9 @@ namespace impactx using namespace amrex::literals; // for _rt and _prt + // shift due to alignment errors of the element + shift_in(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); + // access AoS data such as positions and cpu/id amrex::ParticleReal const x = p.pos(RealAoS::x); amrex::ParticleReal const y = p.pos(RealAoS::y); @@ -162,6 +179,8 @@ namespace impactx py = pyout; pt = ptout; + // undo shift due to alignment errors of the element + shift_out(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); } /** This pushes the reference particle. diff --git a/src/particles/elements/ChrUniformAcc.H b/src/particles/elements/ChrUniformAcc.H index 58a4a8d06..b47acbcda 100644 --- a/src/particles/elements/ChrUniformAcc.H +++ b/src/particles/elements/ChrUniformAcc.H @@ -11,6 +11,7 @@ #define IMPACTX_CHRACC_H #include "particles/ImpactXParticleContainer.H" +#include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thick.H" #include "mixin/nofinalize.H" @@ -26,6 +27,7 @@ namespace impactx struct ChrAcc : public elements::BeamOptic, public elements::Thick, + public elements::Alignment, public elements::NoFinalize { static constexpr auto name = "ChrAcc"; @@ -41,11 +43,23 @@ namespace impactx * = (charge * electric field Ez in V/m) / (m*c^2) * @param bz magnetic field strength in m^(-1) * = (charge * magnetic field Bz in T) / (m*c) + * @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 nslice number of slices used for the application of space charge */ - ChrAcc( amrex::ParticleReal const ds, amrex::ParticleReal const ez, - amrex::ParticleReal const bz, int const nslice ) - : Thick(ds, nslice), m_ez(ez), m_bz(bz) + ChrAcc ( + amrex::ParticleReal ds, + amrex::ParticleReal ez, + amrex::ParticleReal bz, + amrex::ParticleReal dx = 0, + amrex::ParticleReal dy = 0, + amrex::ParticleReal rotation_degree = 0, + int nslice = 1 + ) + : Thick(ds, nslice), + Alignment(dx, dy, rotation_degree), + m_ez(ez), m_bz(bz) { } @@ -70,6 +84,9 @@ namespace impactx using namespace amrex::literals; // for _rt and _prt + // shift due to alignment errors of the element + shift_in(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); + // access AoS data such as positions and cpu/id amrex::ParticleReal const x = p.pos(RealAoS::x); amrex::ParticleReal const y = p.pos(RealAoS::y); @@ -149,6 +166,8 @@ namespace impactx py = py/bgf; pt = pt/bgf; + // undo shift due to alignment errors of the element + shift_out(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); } /** This pushes the reference particle. diff --git a/src/particles/elements/ConstF.H b/src/particles/elements/ConstF.H index 81fc62d71..c0dae3af1 100644 --- a/src/particles/elements/ConstF.H +++ b/src/particles/elements/ConstF.H @@ -11,6 +11,7 @@ #define IMPACTX_CONSTF_H #include "particles/ImpactXParticleContainer.H" +#include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thick.H" #include "mixin/nofinalize.H" @@ -26,6 +27,7 @@ namespace impactx struct ConstF : public elements::BeamOptic, public elements::Thick, + public elements::Alignment, public elements::NoFinalize { static constexpr auto name = "ConstF"; @@ -37,12 +39,24 @@ namespace impactx * @param kx Focusing strength for x in 1/m. * @param ky Focusing strength for y in 1/m. * @param kt Focusing strength for t in 1/m. + * @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 nslice number of slices used for the application of space charge */ - ConstF( amrex::ParticleReal const ds, amrex::ParticleReal const kx, - amrex::ParticleReal const ky, amrex::ParticleReal const kt, - int const nslice ) - : Thick(ds, nslice), m_kx(kx), m_ky(ky), m_kt(kt) + ConstF ( + amrex::ParticleReal ds, + amrex::ParticleReal kx, + amrex::ParticleReal ky, + amrex::ParticleReal kt, + amrex::ParticleReal dx = 0, + amrex::ParticleReal dy = 0, + amrex::ParticleReal rotation_degree = 0, + int nslice = 1 + ) + : Thick(ds, nslice), + Alignment(dx, dy, rotation_degree), + m_kx(kx), m_ky(ky), m_kt(kt) { } @@ -67,6 +81,9 @@ namespace impactx using namespace amrex::literals; // for _rt and _prt + // shift due to alignment errors of the element + shift_in(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); + // access AoS data such as positions and cpu/id amrex::ParticleReal const x = p.pos(RealAoS::x); amrex::ParticleReal const y = p.pos(RealAoS::y); @@ -99,6 +116,8 @@ namespace impactx py = pyout; pt = ptout; + // undo shift due to alignment errors of the element + shift_out(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); } /** This pushes the reference particle. diff --git a/src/particles/elements/DipEdge.H b/src/particles/elements/DipEdge.H index c86b2ee68..9a297cf30 100644 --- a/src/particles/elements/DipEdge.H +++ b/src/particles/elements/DipEdge.H @@ -11,6 +11,7 @@ #define IMPACTX_DIPEDGE_H #include "particles/ImpactXParticleContainer.H" +#include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thin.H" #include "mixin/nofinalize.H" @@ -26,6 +27,7 @@ namespace impactx struct DipEdge : public elements::BeamOptic, public elements::Thin, + public elements::Alignment, public elements::NoFinalize { static constexpr auto name = "DipEdge"; @@ -44,10 +46,21 @@ namespace impactx * @param rc Radius of curvature in m. * @param g Gap parameter in m. * @param K2 Fringe field integral (unitless). + * @param dx horizontal translation error in m + * @param dy vertical translation error in m + * @param rotation_degree rotation error in the transverse plane [degrees] */ - DipEdge( amrex::ParticleReal const psi, amrex::ParticleReal const rc, - amrex::ParticleReal const g, amrex::ParticleReal const K2 ) - : m_psi(psi), m_rc(rc), m_g(g), m_K2(K2) + DipEdge ( + amrex::ParticleReal psi, + amrex::ParticleReal rc, + amrex::ParticleReal g, + amrex::ParticleReal K2, + amrex::ParticleReal dx = 0, + amrex::ParticleReal dy = 0, + amrex::ParticleReal rotation_degree = 0 + ) + : Alignment(dx, dy, rotation_degree), + m_psi(psi), m_rc(rc), m_g(g), m_K2(K2) { } @@ -73,6 +86,9 @@ namespace impactx using namespace amrex::literals; // for _rt and _prt + // shift due to alignment errors of the element + shift_in(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); + // access AoS data such as positions and cpu/id amrex::ParticleReal const x = p.pos(RealAoS::x); amrex::ParticleReal const y = p.pos(RealAoS::y); @@ -92,6 +108,9 @@ namespace impactx // apply edge focusing px = px + R21*x; py = py + R43*y; + + // undo shift due to alignment errors of the element + shift_out(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); } /** This pushes the reference particle. */ diff --git a/src/particles/elements/Drift.H b/src/particles/elements/Drift.H index fdfdd57cd..607ac057f 100644 --- a/src/particles/elements/Drift.H +++ b/src/particles/elements/Drift.H @@ -11,6 +11,7 @@ #define IMPACTX_DRIFT_H #include "particles/ImpactXParticleContainer.H" +#include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thick.H" #include "mixin/nofinalize.H" @@ -26,6 +27,7 @@ namespace impactx struct Drift : public elements::BeamOptic, public elements::Thick, + public elements::Alignment, public elements::NoFinalize { static constexpr auto name = "Drift"; @@ -34,10 +36,20 @@ namespace impactx /** A drift * * @param ds Segment length in m + * @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 nslice number of slices used for the application of space charge */ - Drift( amrex::ParticleReal const ds, int const nslice ) - : Thick(ds, nslice) + Drift ( + amrex::ParticleReal ds, + amrex::ParticleReal dx = 0, + amrex::ParticleReal dy = 0, + amrex::ParticleReal rotation_degree = 0, + int nslice = 1 + ) + : Thick(ds, nslice), + Alignment(dx, dy, rotation_degree) { } @@ -62,6 +74,9 @@ namespace impactx using namespace amrex::literals; // for _rt and _prt + // shift due to alignment errors of the element + shift_in(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); + // access AoS data such as positions and cpu/id amrex::ParticleReal const x = p.pos(RealAoS::x); amrex::ParticleReal const y = p.pos(RealAoS::y); @@ -92,6 +107,8 @@ namespace impactx py = pyout; pt = ptout; + // undo shift due to alignment errors of the element + shift_out(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); } /** This pushes the reference particle. diff --git a/src/particles/elements/ExactDrift.H b/src/particles/elements/ExactDrift.H index 158d9b08f..19f00abe0 100644 --- a/src/particles/elements/ExactDrift.H +++ b/src/particles/elements/ExactDrift.H @@ -11,6 +11,7 @@ #define IMPACTX_EXACTDRIFT_H #include "particles/ImpactXParticleContainer.H" +#include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thick.H" #include "mixin/nofinalize.H" @@ -26,6 +27,7 @@ namespace impactx struct ExactDrift : public elements::BeamOptic, public elements::Thick, + public elements::Alignment, public elements::NoFinalize { static constexpr auto name = "ExactDrift"; @@ -34,10 +36,20 @@ namespace impactx /** A drift using the exact nonlinear transfer map * * @param ds Segment length in m + * @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 nslice number of slices used for the application of space charge */ - ExactDrift( amrex::ParticleReal const ds, int const nslice ) - : Thick(ds, nslice) + ExactDrift ( + amrex::ParticleReal ds, + amrex::ParticleReal dx = 0, + amrex::ParticleReal dy = 0, + amrex::ParticleReal rotation_degree = 0, + int nslice = 1 + ) + : Thick(ds, nslice), + Alignment(dx, dy, rotation_degree) { } @@ -63,6 +75,9 @@ namespace impactx using namespace amrex::literals; // for _rt and _prt + // shift due to alignment errors of the element + shift_in(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); + // access AoS data such as positions and cpu/id amrex::ParticleReal const x = p.pos(RealAoS::x); amrex::ParticleReal const y = p.pos(RealAoS::y); @@ -98,6 +113,8 @@ namespace impactx py = pyout; pt = ptout; + // undo shift due to alignment errors of the element + shift_out(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); } /** This pushes the reference particle. diff --git a/src/particles/elements/ExactSbend.H b/src/particles/elements/ExactSbend.H index e08e3ac59..0b8ee014b 100644 --- a/src/particles/elements/ExactSbend.H +++ b/src/particles/elements/ExactSbend.H @@ -11,6 +11,7 @@ #define IMPACTX_EXACTSBEND_H #include "particles/ImpactXParticleContainer.H" +#include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thick.H" #include "mixin/nofinalize.H" @@ -28,6 +29,7 @@ namespace impactx struct ExactSbend : public elements::BeamOptic, public elements::Thick, + public elements::Alignment, public elements::NoFinalize { static constexpr auto name = "ExactSbend"; @@ -49,11 +51,23 @@ namespace impactx * @param B Magnetic field strength in T. * When B = 0 (default), 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. + * @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 nslice number of slices used for the application of space charge */ - ExactSbend( amrex::ParticleReal const ds, amrex::ParticleReal const phi, - amrex::ParticleReal const B, int const nslice ) - : Thick(ds, nslice), m_phi(phi * degree2rad), m_B(B) + ExactSbend ( + amrex::ParticleReal ds, + amrex::ParticleReal phi, + amrex::ParticleReal B, + amrex::ParticleReal dx = 0, + amrex::ParticleReal dy = 0, + amrex::ParticleReal rotation_degree = 0, + int nslice = 1 + ) + : Thick(ds, nslice), + Alignment(dx, dy, rotation_degree), + m_phi(phi * degree2rad), m_B(B) { } @@ -78,6 +92,9 @@ namespace impactx using namespace amrex::literals; // for _rt and _prt + // shift due to alignment errors of the element + shift_in(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); + // access AoS data such as positions and cpu/id amrex::ParticleReal const x = p.pos(RealAoS::x); amrex::ParticleReal const y = p.pos(RealAoS::y); @@ -122,6 +139,8 @@ namespace impactx py = pyout; pt = ptout; + // undo shift due to alignment errors of the element + shift_out(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); } /** This pushes the reference particle. diff --git a/src/particles/elements/Kicker.H b/src/particles/elements/Kicker.H index 1cb3a34ba..bd735747b 100644 --- a/src/particles/elements/Kicker.H +++ b/src/particles/elements/Kicker.H @@ -11,6 +11,7 @@ #define IMPACTX_KICKER_H #include "particles/ImpactXParticleContainer.H" +#include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thin.H" #include "mixin/nofinalize.H" @@ -26,6 +27,7 @@ namespace impactx struct Kicker : public elements::BeamOptic, public elements::Thin, + public elements::Alignment, public elements::NoFinalize { static constexpr auto name = "Kicker"; @@ -43,11 +45,20 @@ namespace impactx * @param xkick Strength of horizontal kick * @param ykick Strength of vertical kick * @param unit units of xkick and ykick + * @param dx horizontal translation error in m + * @param dy vertical translation error in m + * @param rotation_degree rotation error in the transverse plane [degrees] */ - Kicker (amrex::ParticleReal xkick, - amrex::ParticleReal ykick, - UnitSystem unit) - : m_xkick(xkick), m_ykick(ykick), m_unit(unit) + Kicker ( + amrex::ParticleReal xkick, + amrex::ParticleReal ykick, + UnitSystem unit, + amrex::ParticleReal dx = 0, + amrex::ParticleReal dy = 0, + amrex::ParticleReal rotation_degree = 0 + ) + : Alignment(dx, dy, rotation_degree), + m_xkick(xkick), m_ykick(ykick), m_unit(unit) { } @@ -73,6 +84,9 @@ namespace impactx { using namespace amrex::literals; // for _rt and _prt + // shift due to alignment errors of the element + shift_in(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); + // access AoS data such as positions and cpu/id amrex::ParticleReal const x = p.pos(RealAoS::x); amrex::ParticleReal const y = p.pos(RealAoS::y); @@ -105,6 +119,9 @@ namespace impactx px = pxout; py = pyout; pt = ptout; + + // undo shift due to alignment errors of the element + shift_out(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); } /** This pushes the reference particle. */ diff --git a/src/particles/elements/Multipole.H b/src/particles/elements/Multipole.H index ef487a8c1..a198b8fa8 100644 --- a/src/particles/elements/Multipole.H +++ b/src/particles/elements/Multipole.H @@ -11,6 +11,7 @@ #define IMPACTX_MULTIPOLE_H #include "particles/ImpactXParticleContainer.H" +#include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thin.H" #include "mixin/nofinalize.H" @@ -26,6 +27,7 @@ namespace impactx struct Multipole : public elements::BeamOptic, public elements::Thin, + public elements::Alignment, public elements::NoFinalize { static constexpr auto name = "Multipole"; @@ -36,11 +38,20 @@ namespace impactx * @param multipole index m (m=1 dipole, m=2 quadrupole, m=3 sextupole etc.) * @param K_normal Integrated normal multipole coefficient (1/meter^m) * @param K_skew Integrated skew multipole coefficient (1/meter^m) + * @param dx horizontal translation error in m + * @param dy vertical translation error in m + * @param rotation_degree rotation error in the transverse plane [degrees] */ - Multipole( int const multipole, - amrex::ParticleReal const K_normal, - amrex::ParticleReal const K_skew ) - : m_multipole(multipole), m_Kn(K_normal), m_Ks(K_skew) + Multipole ( + int multipole, + amrex::ParticleReal K_normal, + amrex::ParticleReal K_skew, + amrex::ParticleReal dx = 0, + amrex::ParticleReal dy = 0, + amrex::ParticleReal rotation_degree = 0 + ) + : Alignment(dx, dy, rotation_degree), + m_multipole(multipole), m_Kn(K_normal), m_Ks(K_skew) { // compute factorial of multipole index int const m = m_multipole - 1; @@ -72,6 +83,9 @@ namespace impactx using namespace amrex::literals; // for _rt and _prt + // shift due to alignment errors of the element + shift_in(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); + // a complex type with two amrex::ParticleReal using Complex = amrex::GpuComplex; @@ -115,6 +129,8 @@ namespace impactx py = pyout; pt = ptout; + // undo shift due to alignment errors of the element + shift_out(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); } /** This pushes the reference particle. */ diff --git a/src/particles/elements/NonlinearLens.H b/src/particles/elements/NonlinearLens.H index 51fb3861b..0bae32d3d 100644 --- a/src/particles/elements/NonlinearLens.H +++ b/src/particles/elements/NonlinearLens.H @@ -11,6 +11,7 @@ #define IMPACTX_NONLINEARLENS_H #include "particles/ImpactXParticleContainer.H" +#include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thin.H" #include "mixin/nofinalize.H" @@ -26,6 +27,7 @@ namespace impactx struct NonlinearLens : public elements::BeamOptic, public elements::Thin, + public elements::Alignment, public elements::NoFinalize { static constexpr auto name = "NonlinearLens"; @@ -40,10 +42,19 @@ namespace impactx * * @param knll integrated strength of the nonlinear lens (m) * @param cnll distance of singularities from the origin (m) + * @param dx horizontal translation error in m + * @param dy vertical translation error in m + * @param rotation_degree rotation error in the transverse plane [degrees] */ - NonlinearLens( amrex::ParticleReal const knll, - amrex::ParticleReal const cnll ) - : m_knll(knll), m_cnll(cnll) + NonlinearLens ( + amrex::ParticleReal knll, + amrex::ParticleReal cnll, + amrex::ParticleReal dx = 0, + amrex::ParticleReal dy = 0, + amrex::ParticleReal rotation_degree = 0 + ) + : Alignment(dx, dy, rotation_degree), + m_knll(knll), m_cnll(cnll) { } @@ -72,6 +83,9 @@ namespace impactx // a complex type with two amrex::ParticleReal using Complex = amrex::GpuComplex; + // shift due to alignment errors of the element + shift_in(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); + // access AoS data such as positions and cpu/id amrex::ParticleReal const x = p.pos(RealAoS::x); amrex::ParticleReal const y = p.pos(RealAoS::y); @@ -125,6 +139,8 @@ namespace impactx py = pyout; pt = ptout; + // undo shift due to alignment errors of the element + shift_out(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); } /** This pushes the reference particle. */ diff --git a/src/particles/elements/Quad.H b/src/particles/elements/Quad.H index c15c27bbf..75d82ae7b 100644 --- a/src/particles/elements/Quad.H +++ b/src/particles/elements/Quad.H @@ -11,6 +11,7 @@ #define IMPACTX_QUAD_H #include "particles/ImpactXParticleContainer.H" +#include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thick.H" #include "mixin/nofinalize.H" @@ -26,6 +27,7 @@ namespace impactx struct Quad : public elements::BeamOptic, public elements::Thick, + public elements::Alignment, public elements::NoFinalize { static constexpr auto name = "Quad"; @@ -38,11 +40,22 @@ namespace impactx * = (gradient in T/m) / (rigidity in T-m) * k > 0 horizontal focusing * k < 0 horizontal defocusing + * @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 nslice number of slices used for the application of space charge */ - Quad( amrex::ParticleReal const ds, amrex::ParticleReal const k, - int const nslice ) - : Thick(ds, nslice), m_k(k) + Quad ( + amrex::ParticleReal ds, + amrex::ParticleReal k, + amrex::ParticleReal dx = 0, + amrex::ParticleReal dy = 0, + amrex::ParticleReal rotation_degree = 0, + int nslice = 1 + ) + : Thick(ds, nslice), + Alignment(dx, dy, rotation_degree), + m_k(k) { } @@ -59,14 +72,18 @@ namespace impactx */ AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void operator() ( - PType& AMREX_RESTRICT p, - amrex::ParticleReal & AMREX_RESTRICT px, - amrex::ParticleReal & AMREX_RESTRICT py, - amrex::ParticleReal & AMREX_RESTRICT pt, - RefPart const & refpart) const { - + PType& AMREX_RESTRICT p, + amrex::ParticleReal & AMREX_RESTRICT px, + amrex::ParticleReal & AMREX_RESTRICT py, + amrex::ParticleReal & AMREX_RESTRICT pt, + RefPart const & refpart + ) const + { using namespace amrex::literals; // for _rt and _prt + // shift due to alignment errors of the element + shift_in(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); + // access AoS data such as positions and cpu/id amrex::ParticleReal const x = p.pos(RealAoS::x); amrex::ParticleReal const y = p.pos(RealAoS::y); @@ -114,6 +131,8 @@ namespace impactx py = pyout; pt = ptout; + // undo shift due to alignment errors of the element + shift_out(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); } /** This pushes the reference particle. diff --git a/src/particles/elements/RFCavity.H b/src/particles/elements/RFCavity.H index 86ac3154a..b4e4025c8 100644 --- a/src/particles/elements/RFCavity.H +++ b/src/particles/elements/RFCavity.H @@ -12,6 +12,7 @@ #include "particles/ImpactXParticleContainer.H" #include "particles/integrators/Integrators.H" +#include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thick.H" @@ -102,7 +103,8 @@ namespace RFCavityData struct RFCavity : public elements::BeamOptic, - public elements::Thick + public elements::Thick, + public elements::Alignment { static constexpr auto name = "RFCavity"; using PType = ImpactXParticleContainer::ParticleType; @@ -115,6 +117,9 @@ namespace RFCavityData * @param phase RF driven phase in deg * @param cos_coef TODO * @param sin_coef TODO + * @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 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 @@ -126,10 +131,14 @@ namespace RFCavityData amrex::ParticleReal phase, std::vector cos_coef, std::vector sin_coef, + amrex::ParticleReal dx = 0, + amrex::ParticleReal dy = 0, + amrex::ParticleReal rotation_degree = 0, int mapsteps = 1, int nslice = 1 ) : Thick(ds, nslice), + Alignment(dx, dy, rotation_degree), m_escale(escale), m_freq(freq), m_phase(phase), m_mapsteps(mapsteps) { // next created RF cavity has another id for its data @@ -185,6 +194,9 @@ namespace RFCavityData { using namespace amrex::literals; // for _rt and _prt + // shift due to alignment errors of the element + shift_in(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); + // access AoS data such as positions and cpu/id amrex::ParticleReal const x = p.pos(RealAoS::x); amrex::ParticleReal const y = p.pos(RealAoS::y); @@ -222,6 +234,9 @@ namespace RFCavityData px = pxout; py = pyout; pt = ptout; + + // undo shift due to alignment errors of the element + shift_out(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); } /** This pushes the reference particle. diff --git a/src/particles/elements/Sbend.H b/src/particles/elements/Sbend.H index 61a21f893..f30a4723f 100644 --- a/src/particles/elements/Sbend.H +++ b/src/particles/elements/Sbend.H @@ -11,6 +11,7 @@ #define IMPACTX_SBEND_H #include "particles/ImpactXParticleContainer.H" +#include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thick.H" #include "mixin/nofinalize.H" @@ -27,6 +28,7 @@ namespace impactx struct Sbend : public elements::BeamOptic, public elements::Thick, + public elements::Alignment, public elements::NoFinalize { static constexpr auto name = "Sbend"; @@ -36,11 +38,22 @@ namespace impactx * * @param ds Segment length in m. * @param rc Radius of curvature in m. + * @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 nslice number of slices used for the application of space charge */ - Sbend( amrex::ParticleReal const ds, amrex::ParticleReal const rc, - int const nslice) - : Thick(ds, nslice), m_rc(rc) + Sbend ( + amrex::ParticleReal ds, + amrex::ParticleReal rc, + amrex::ParticleReal dx = 0, + amrex::ParticleReal dy = 0, + amrex::ParticleReal rotation_degree = 0, + int nslice = 1 + ) + : Thick(ds, nslice), + Alignment(dx, dy, rotation_degree), + m_rc(rc) { } @@ -65,6 +78,9 @@ namespace impactx using namespace amrex::literals; // for _rt and _prt + // shift due to alignment errors of the element + shift_in(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); + // access AoS data such as positions and cpu/id amrex::ParticleReal const x = p.pos(RealAoS::x); amrex::ParticleReal const y = p.pos(RealAoS::y); @@ -107,6 +123,8 @@ namespace impactx py = pyout; pt = ptout; + // undo shift due to alignment errors of the element + shift_out(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); } /** This pushes the reference particle. diff --git a/src/particles/elements/ShortRF.H b/src/particles/elements/ShortRF.H index 02d161d21..a8b8c81cf 100644 --- a/src/particles/elements/ShortRF.H +++ b/src/particles/elements/ShortRF.H @@ -11,6 +11,7 @@ #define IMPACTX_SHORTRF_H #include "particles/ImpactXParticleContainer.H" +#include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thin.H" #include "mixin/nofinalize.H" @@ -26,6 +27,7 @@ namespace impactx struct ShortRF : public elements::BeamOptic, public elements::Thin, + public elements::Alignment, public elements::NoFinalize { static constexpr auto name = "ShortRF"; @@ -39,10 +41,20 @@ namespace impactx * phi = 0: maximum energy gain (on-crest) * phi = -90 deg: zero-crossing for bunching * phi = 90 deg: zero-crossing for debunching + * @param dx horizontal translation error in m + * @param dy vertical translation error in m + * @param rotation_degree rotation error in the transverse plane [degrees] */ - ShortRF( amrex::ParticleReal const V, amrex::ParticleReal const freq, - amrex::ParticleReal const phase ) - : m_V(V), m_freq(freq), m_phase(phase) + ShortRF ( + amrex::ParticleReal V, + amrex::ParticleReal freq, + amrex::ParticleReal phase, + amrex::ParticleReal dx = 0, + amrex::ParticleReal dy = 0, + amrex::ParticleReal rotation_degree = 0 + ) + : Alignment(dx, dy, rotation_degree), + m_V(V), m_freq(freq), m_phase(phase) { } @@ -68,6 +80,9 @@ namespace impactx using namespace amrex::literals; // for _rt and _prt + // shift due to alignment errors of the element + shift_in(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); + // access AoS data such as positions and cpu/id amrex::ParticleReal const x = p.pos(RealAoS::x); amrex::ParticleReal const y = p.pos(RealAoS::y); @@ -115,6 +130,8 @@ namespace impactx py = py/bgf; pt = pt/bgf; + // undo shift due to alignment errors of the element + shift_out(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); } /** This pushes the reference particle. */ diff --git a/src/particles/elements/SoftQuad.H b/src/particles/elements/SoftQuad.H index 481a72368..6edf73e8f 100644 --- a/src/particles/elements/SoftQuad.H +++ b/src/particles/elements/SoftQuad.H @@ -12,6 +12,7 @@ #include "particles/ImpactXParticleContainer.H" #include "particles/integrators/Integrators.H" +#include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thick.H" @@ -111,7 +112,8 @@ namespace SoftQuadrupoleData struct SoftQuadrupole : public elements::BeamOptic, - public elements::Thick + public elements::Thick, + public elements::Alignment { static constexpr auto name = "SoftQuadrupole"; using PType = ImpactXParticleContainer::ParticleType; @@ -122,6 +124,9 @@ namespace SoftQuadrupoleData * @param gscale Scaling factor for on-axis field gradient Bz in 1/m^2 * @param cos_coef cosine coefficients in Fourier expansion of on-axis magnetic field Bz * @param sin_coef sine coefficients in Fourier expansion of on-axis magnetic field Bz + * @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 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 @@ -131,10 +136,14 @@ namespace SoftQuadrupoleData amrex::ParticleReal gscale, std::vector cos_coef, std::vector sin_coef, + amrex::ParticleReal dx = 0, + amrex::ParticleReal dy = 0, + amrex::ParticleReal rotation_degree = 0, int mapsteps = 1, int nslice = 1 ) : Thick(ds, nslice), + Alignment(dx, dy, rotation_degree), m_gscale(gscale), m_mapsteps(mapsteps), m_id(SoftQuadrupoleData::next_id) { // next created soft quad has another id for its data @@ -190,6 +199,9 @@ namespace SoftQuadrupoleData { using namespace amrex::literals; // for _rt and _prt + // shift due to alignment errors of the element + shift_in(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); + // access AoS data such as positions and cpu/id amrex::ParticleReal const x = p.pos(RealAoS::x); amrex::ParticleReal const y = p.pos(RealAoS::y); @@ -227,6 +239,9 @@ namespace SoftQuadrupoleData px = pxout; py = pyout; pt = ptout; + + // undo shift due to alignment errors of the element + shift_out(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); } /** This pushes the reference particle. diff --git a/src/particles/elements/SoftSol.H b/src/particles/elements/SoftSol.H index 4e617efdd..d7c0d25db 100644 --- a/src/particles/elements/SoftSol.H +++ b/src/particles/elements/SoftSol.H @@ -12,6 +12,7 @@ #include "particles/ImpactXParticleContainer.H" #include "particles/integrators/Integrators.H" +#include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thick.H" @@ -116,7 +117,8 @@ namespace SoftSolenoidData struct SoftSolenoid : public elements::BeamOptic, - public elements::Thick + public elements::Thick, + public elements::Alignment { static constexpr auto name = "SoftSolenoid"; using PType = ImpactXParticleContainer::ParticleType; @@ -127,6 +129,9 @@ namespace SoftSolenoidData * @param bscale Scaling factor for on-axis magnetic field Bz in 1/m * @param cos_coef cosine coefficients in Fourier expansion of on-axis magnetic field Bz * @param sin_coef sine coefficients in Fourier expansion of on-axis magnetic field Bz + * @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 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 @@ -136,10 +141,14 @@ namespace SoftSolenoidData amrex::ParticleReal bscale, std::vector cos_coef, std::vector sin_coef, + amrex::ParticleReal dx = 0, + amrex::ParticleReal dy = 0, + amrex::ParticleReal rotation_degree = 0, int mapsteps = 1, int nslice = 1 ) : Thick(ds, nslice), + Alignment(dx, dy, rotation_degree), m_bscale(bscale), m_mapsteps(mapsteps), m_id(SoftSolenoidData::next_id) { // next created soft solenoid has another id for its data @@ -195,6 +204,9 @@ namespace SoftSolenoidData { using namespace amrex::literals; // for _rt and _prt + // shift due to alignment errors of the element + shift_in(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); + // access AoS data such as positions and cpu/id amrex::ParticleReal const x = p.pos(RealAoS::x); amrex::ParticleReal const y = p.pos(RealAoS::y); @@ -232,6 +244,9 @@ namespace SoftSolenoidData px = pxout; py = pyout; pt = ptout; + + // undo shift due to alignment errors of the element + shift_out(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); } /** This pushes the reference particle. diff --git a/src/particles/elements/Sol.H b/src/particles/elements/Sol.H index bb850e01b..334e223a7 100644 --- a/src/particles/elements/Sol.H +++ b/src/particles/elements/Sol.H @@ -11,6 +11,7 @@ #define IMPACTX_SOL_H #include "particles/ImpactXParticleContainer.H" +#include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thick.H" #include "mixin/nofinalize.H" @@ -27,6 +28,7 @@ namespace impactx struct Sol : public elements::BeamOptic, public elements::Thick, + public elements::Alignment, public elements::NoFinalize { static constexpr auto name = "Sol"; @@ -37,14 +39,22 @@ namespace impactx * @param ds Segment length in m. * @param ks Solenoid strength in m^(-1) (MADX convention) * = (magnetic field Bz in T) / (rigidity in T-m) + * @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 nslice number of slices used for the application of space charge */ Sol ( amrex::ParticleReal ds, amrex::ParticleReal ks, - int nslice + amrex::ParticleReal dx = 0, + amrex::ParticleReal dy = 0, + amrex::ParticleReal rotation_degree = 0, + int nslice = 1 ) - : Thick(ds, nslice), m_ks(ks) + : Thick(ds, nslice), + Alignment(dx, dy, rotation_degree), + m_ks(ks) { } @@ -70,6 +80,9 @@ namespace impactx { using namespace amrex::literals; // for _rt and _prt + // shift due to alignment errors of the element + shift_in(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); + // access AoS data such as positions and cpu/id amrex::ParticleReal const x = p.pos(RealAoS::x); amrex::ParticleReal const y = p.pos(RealAoS::y); @@ -125,6 +138,9 @@ namespace impactx px = pxout; py = pyout; pt = ptout; + + // undo shift due to alignment errors of the element + shift_out(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); } /** This pushes the reference particle. diff --git a/src/particles/elements/ThinDipole.H b/src/particles/elements/ThinDipole.H index 5489d4b57..a7a06e716 100644 --- a/src/particles/elements/ThinDipole.H +++ b/src/particles/elements/ThinDipole.H @@ -11,6 +11,7 @@ #define IMPACTX_THINDIPOLE_H #include "particles/ImpactXParticleContainer.H" +#include "mixin/alignment.H" #include "mixin/beamoptic.H" #include "mixin/thin.H" #include "mixin/nofinalize.H" @@ -23,6 +24,7 @@ namespace impactx struct ThinDipole : public elements::BeamOptic, public elements::Thin, + public elements::Alignment, public elements::NoFinalize { static constexpr auto name = "ThinDipole"; @@ -39,10 +41,20 @@ namespace impactx * * @param theta - the total bending angle (degrees) * @param rc - the curvature radius (m) + * @param dx horizontal translation error in m + * @param dy vertical translation error in m + * @param rotation_degree rotation error in the transverse plane [degrees] */ - ThinDipole( amrex::ParticleReal const theta, - amrex::ParticleReal const rc) - : m_theta(theta * degree2rad), m_rc(rc) + ThinDipole ( + amrex::ParticleReal theta, + amrex::ParticleReal rc, + amrex::ParticleReal dx = 0, + amrex::ParticleReal dy = 0, + amrex::ParticleReal rotation_degree = 0 + ) + : Alignment(dx, dy, rotation_degree), + m_theta(theta * degree2rad), + m_rc(rc) { } @@ -68,6 +80,9 @@ namespace impactx using namespace amrex::literals; // for _rt and _prt + // shift due to alignment errors of the element + shift_in(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); + // access AoS data such as positions and cpu/id amrex::ParticleReal const x = p.pos(RealAoS::x); amrex::ParticleReal const y = p.pos(RealAoS::y); @@ -104,6 +119,8 @@ namespace impactx py = pyout; pt = ptout; + // undo shift due to alignment errors of the element + shift_out(p.pos(RealAoS::x), p.pos(RealAoS::y), px, py); } /** This pushes the reference particle. */ diff --git a/src/particles/elements/mixin/alignment.H b/src/particles/elements/mixin/alignment.H new file mode 100644 index 000000000..d108c4e1b --- /dev/null +++ b/src/particles/elements/mixin/alignment.H @@ -0,0 +1,152 @@ +/* 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_ELEMENTS_MIXIN_ALIGNMENT_H +#define IMPACTX_ELEMENTS_MIXIN_ALIGNMENT_H + +#include "particles/ImpactXParticleContainer.H" + +#include + +#include +#include +#include + + +namespace impactx::elements +{ + /** This is a helper class for lattice elements with horizontal/vertical alignment errors + */ + struct Alignment + { + static constexpr amrex::ParticleReal degree2rad = ablastr::constant::math::pi / 180.0; + + /** A finite-length element + * + * @param dx horizontal translation error in m + * @param dy vertical translation error in m + * @param rotation_degree rotation error in the transverse plane [degrees] + */ + Alignment ( + amrex::ParticleReal dx, + amrex::ParticleReal dy, + amrex::ParticleReal rotation_degree + ) + : m_dx(dx), m_dy(dy), m_rotation(rotation_degree * degree2rad) + { + } + + Alignment () = default; + Alignment (Alignment const &) = default; + Alignment& operator= (Alignment const &) = default; + Alignment (Alignment&&) = default; + Alignment& operator= (Alignment&& rhs) = default; + + ~Alignment () = default; + + /** Shift the particle into the alignment error frame + * + * @param[inout] x horizontal position relative to reference particle + * @param[inout] y vertical position relative to reference particle + * @param[inout] px horizontal momentum relative to reference particle + * @param[inout] py vertical momentum relative to reference particle + */ + AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE + void shift_in ( + amrex::ParticleReal & AMREX_RESTRICT x, + amrex::ParticleReal & AMREX_RESTRICT y, + amrex::ParticleReal & AMREX_RESTRICT px, + amrex::ParticleReal & AMREX_RESTRICT py + ) const + { + auto const [sin_rotation, cos_rotation] = amrex::Math::sincos(m_rotation); + + // position + amrex::ParticleReal const xc = x - m_dx; + amrex::ParticleReal const yc = y - m_dy; + x = xc * cos_rotation + yc * sin_rotation; + y = -xc * sin_rotation + yc * cos_rotation; + + // momentum + amrex::ParticleReal const pxc = px; + amrex::ParticleReal const pyc = py; + px = pxc * cos_rotation + pyc * sin_rotation; + py = -pxc * sin_rotation + pyc * cos_rotation; + } + + /** Shift the particle out of the alignment error frame + * + * @param[inout] x horizontal position relative to reference particle + * @param[inout] y vertical position relative to reference particle + * @param[inout] px horizontal momentum relative to reference particle + * @param[inout] py vertical momentum relative to reference particle + */ + AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE + void shift_out ( + amrex::ParticleReal & AMREX_RESTRICT x, + amrex::ParticleReal & AMREX_RESTRICT y, + amrex::ParticleReal & AMREX_RESTRICT px, + amrex::ParticleReal & AMREX_RESTRICT py + ) const + { + auto const [sin_rotation, cos_rotation] = amrex::Math::sincos(m_rotation); + + // position + amrex::ParticleReal const xc = x; + amrex::ParticleReal const yc = y; + x = xc * cos_rotation - yc * sin_rotation; + y = xc * sin_rotation + yc * cos_rotation; + x += m_dx; + y += m_dy; + + // momentum + amrex::ParticleReal const pxc = px; + amrex::ParticleReal const pyc = py; + px = pxc * cos_rotation - pyc * sin_rotation; + py = pxc * sin_rotation + pyc * cos_rotation; + } + + /** Horizontal translation error + * + * @return horizontal translation error in m + */ + AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE + amrex::ParticleReal dx () const + { + return m_dx; + } + + /** Vertical translation error + * + * @return vertical translation error in m + */ + AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE + amrex::ParticleReal dy () const + { + return m_dy; + } + + /** rotation error in the transverse plane + * + * @return rotation error in the transverse plane [degrees] + */ + AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE + amrex::ParticleReal rotation () const + { + return m_rotation / degree2rad; + } + + amrex::ParticleReal m_dx = 0; //! horizontal translation error [m] + amrex::ParticleReal m_dy = 0; //! vertical translation error [m] + amrex::ParticleReal m_rotation = 0; //! rotation error in the transverse plane [rad] + }; + +} // namespace impactx::elements + +#endif // IMPACTX_ELEMENTS_MIXIN_ALIGNMENT_H diff --git a/src/particles/elements/mixin/thick.H b/src/particles/elements/mixin/thick.H index aa04f5ba4..4572d2a67 100644 --- a/src/particles/elements/mixin/thick.H +++ b/src/particles/elements/mixin/thick.H @@ -27,7 +27,10 @@ namespace impactx::elements * @param ds Segment length in m * @param nslice number of slices used for the application of space charge */ - Thick(amrex::ParticleReal const ds, int const nslice ) + Thick ( + amrex::ParticleReal ds, + int nslice + ) : m_ds(ds), m_nslice(nslice) { } @@ -52,7 +55,6 @@ namespace impactx::elements return m_ds; } - protected: amrex::ParticleReal m_ds; //! segment length in m int m_nslice; //! number of slices used for the application of space charge }; diff --git a/src/python/elements.cpp b/src/python/elements.cpp index 84905ea9c..67ac277d5 100644 --- a/src/python/elements.cpp +++ b/src/python/elements.cpp @@ -92,22 +92,61 @@ void init_elements(py::module& m) py::class_(me, "Thick") .def(py::init< - amrex::ParticleReal const, - amrex::ParticleReal const + amrex::ParticleReal, + amrex::ParticleReal >(), - py::arg("ds"), py::arg("nslice") = 1, + py::arg("ds"), + py::arg("nslice") = 1, "Mixin class for lattice elements with finite length." ) - .def_property_readonly("nslice", &elements::Thick::nslice) - .def_property_readonly("ds", &elements::Thick::ds) + .def_property("ds", + [](elements::Thick & th) { return th.m_ds; }, + [](elements::Thick & th, amrex::ParticleReal ds) { th.m_ds = ds; }, + "segment length in m" + ) + .def_property("nslice", + [](elements::Thick & th) { return th.m_nslice; }, + [](elements::Thick & th, int nslice) { th.m_nslice = nslice; }, + "number of slices used for the application of space charge" + ) ; py::class_(me, "Thin") .def(py::init<>(), "Mixin class for lattice elements with zero length." ) - .def_property_readonly("nslice", &elements::Thin::nslice) - .def_property_readonly("ds", &elements::Thin::ds) + .def_property_readonly("ds", + &elements::Thin::ds, + "segment length in m" + ) + .def_property_readonly("nslice", + &elements::Thin::nslice, + "number of slices used for the application of space charge" + ) + ; + + py::class_(me, "Alignment") + .def(py::init<>(), + "Mixin class for lattice elements with horizontal/vertical alignment errors." + ) + .def_property("dx", + [](elements::Alignment & a) { return a.dx(); }, + [](elements::Alignment & a, amrex::ParticleReal dx) { a.m_dx = dx; }, + "horizontal translation error in m" + ) + .def_property("dy", + [](elements::Alignment & a) { return a.dy(); }, + [](elements::Alignment & a, amrex::ParticleReal dy) { a.m_dy = dy; }, + "vertical translation error in m" + ) + .def_property("rotation", + [](elements::Alignment & a) { return a.rotation(); }, + [](elements::Alignment & a, amrex::ParticleReal rotation_degree) + { + a.m_rotation = rotation_degree * elements::Alignment::degree2rad; + }, + "rotation error in the transverse plane in degree" + ) ; // diagnostics @@ -115,7 +154,9 @@ void init_elements(py::module& m) py::class_ py_BeamMonitor(me, "BeamMonitor"); py_BeamMonitor .def(py::init(), - py::arg("name"), py::arg("backend")="default", py::arg("encoding")="g", + py::arg("name"), + py::arg("backend") = "default", + py::arg("encoding") = "g", "This element writes the particle beam out to openPMD data." ) ; @@ -123,12 +164,16 @@ void init_elements(py::module& m) // beam optics - py::class_ py_Aperture(me, "Aperture"); + py::class_ py_Aperture(me, "Aperture"); py_Aperture .def(py::init([]( amrex::ParticleReal xmax, amrex::ParticleReal ymax, - std::string shape) + std::string shape, + amrex::ParticleReal dx, + amrex::ParticleReal dy, + amrex::ParticleReal rotation_degree + ) { if (shape != "rectangular" && shape != "elliptical") throw std::runtime_error("shape must be \"rectangular\" or \"elliptical\""); @@ -136,60 +181,104 @@ void init_elements(py::module& m) Aperture::Shape s = shape == "rectangular" ? Aperture::Shape::rectangular : Aperture::Shape::elliptical; - return new Aperture(xmax, ymax, s); + return new Aperture(xmax, ymax, s, dx, dy, rotation_degree); }), - py::arg("xmax"), py::arg("ymax"), py::arg("shape") = "rectangular", + py::arg("xmax"), + py::arg("ymax"), + py::arg("shape") = "rectangular", + py::arg("dx") = 0, + py::arg("dy") = 0, + py::arg("rotation") = 0, "A short collimator element applying a transverse aperture boundary." ) ; register_beamoptics_push(py_Aperture); - py::class_ py_ChrDrift(me, "ChrDrift"); + py::class_ py_ChrDrift(me, "ChrDrift"); py_ChrDrift .def(py::init< - amrex::ParticleReal const, - int const >(), - py::arg("ds"), py::arg("nslice") = 1, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + int + >(), + py::arg("ds"), + py::arg("dx") = 0, + py::arg("dy") = 0, + py::arg("rotation") = 0, + py::arg("nslice") = 1, "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(py::init< - amrex::ParticleReal const, - amrex::ParticleReal const, - int const, - int const>(), - py::arg("ds"), py::arg("k"), py::arg("units") = 0, py::arg("nslice") = 1, + amrex::ParticleReal, + amrex::ParticleReal, + int, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + int + >(), + py::arg("ds"), + py::arg("k"), + py::arg("units") = 0, + py::arg("dx") = 0, + py::arg("dy") = 0, + py::arg("rotation") = 0, + py::arg("nslice") = 1, "A Quadrupole magnet with chromatic effects included." ) ; register_beamoptics_push(py_ChrQuad); - py::class_ py_ChrAcc(me, "ChrAcc"); + py::class_ py_ChrAcc(me, "ChrAcc"); py_ChrAcc .def(py::init< - amrex::ParticleReal const, - amrex::ParticleReal const, - amrex::ParticleReal const, - int const>(), - py::arg("ds"), py::arg("ez"), py::arg("bz"), py::arg("nslice") = 1, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + int + >(), + py::arg("ds"), + py::arg("ez"), + py::arg("bz"), + py::arg("dx") = 0, + py::arg("dy") = 0, + py::arg("rotation") = 0, + py::arg("nslice") = 1, "A region of Uniform Acceleration, with chromatic effects included." ) ; register_beamoptics_push(py_ChrAcc); - py::class_ py_ConstF(me, "ConstF"); + py::class_ py_ConstF(me, "ConstF"); py_ConstF .def(py::init< - amrex::ParticleReal const, - amrex::ParticleReal const, - amrex::ParticleReal const, - amrex::ParticleReal const, - int const >(), - py::arg("ds"), py::arg("kx"), py::arg("ky"), py::arg("kt"), py::arg("nslice") = 1, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + int + >(), + py::arg("ds"), + py::arg("kx"), + py::arg("ky"), + py::arg("kt"), + py::arg("dx") = 0, + py::arg("dy") = 0, + py::arg("rotation") = 0, + py::arg("nslice") = 1, "A linear Constant Focusing element." ) .def_property("kx", @@ -210,60 +299,100 @@ 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(py::init< - amrex::ParticleReal const, - amrex::ParticleReal const, - amrex::ParticleReal const, - amrex::ParticleReal const>(), - py::arg("psi"), py::arg("rc"), py::arg("g"), py::arg("K2"), + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal + >(), + py::arg("psi"), + py::arg("rc"), + py::arg("g"), + py::arg("K2"), + py::arg("dx") = 0, + py::arg("dy") = 0, + py::arg("rotation") = 0, "Edge focusing associated with bend entry or exit." ) ; register_beamoptics_push(py_DipEdge); - py::class_ py_Drift(me, "Drift"); + py::class_ py_Drift(me, "Drift"); py_Drift .def(py::init< - amrex::ParticleReal const, - int const >(), - py::arg("ds"), py::arg("nslice") = 1, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + int + >(), + py::arg("ds"), + py::arg("dx") = 0, + py::arg("dy") = 0, + py::arg("rotation") = 0, + py::arg("nslice") = 1, "A drift." ) ; register_beamoptics_push(py_Drift); - py::class_ py_ExactDrift(me, "ExactDrift"); + py::class_ py_ExactDrift(me, "ExactDrift"); py_ExactDrift .def(py::init< - amrex::ParticleReal const, - int const >(), - py::arg("ds"), py::arg("nslice") = 1, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + int + >(), + py::arg("ds"), + py::arg("dx") = 0, + py::arg("dy") = 0, + py::arg("rotation") = 0, + py::arg("nslice") = 1, "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(py::init< - amrex::ParticleReal const, - amrex::ParticleReal const, - amrex::ParticleReal const, - int const>(), - py::arg("ds"), py::arg("phi"), py::arg("B") = 0.0, py::arg("nslice") = 1, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + int + >(), + py::arg("ds"), + py::arg("phi"), + py::arg("B") = 0.0, + py::arg("dx") = 0, + py::arg("dy") = 0, + py::arg("rotation") = 0, + py::arg("nslice") = 1, "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." ) ; register_beamoptics_push(py_ExactSbend); - py::class_ py_Kicker(me, "Kicker"); + py::class_ py_Kicker(me, "Kicker"); py_Kicker .def(py::init([]( amrex::ParticleReal xkick, amrex::ParticleReal ykick, - std::string const & units) + std::string const & units, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal + ) { if (units != "dimensionless" && units != "T-m") throw std::runtime_error(R"(units must be "dimensionless" or "T-m")"); @@ -273,19 +402,33 @@ void init_elements(py::module& m) Kicker::UnitSystem::Tm; return new Kicker(xkick, ykick, u); }), - py::arg("xkick"), py::arg("ykick"), py::arg("units") = "dimensionless", + py::arg("xkick"), + py::arg("ykick"), + py::arg("units") = "dimensionless", + py::arg("dx") = 0, + py::arg("dy") = 0, + py::arg("rotation") = 0, R"(A thin transverse kicker element. Kicks are for units "dimensionless" or in "T-m".)" ) ; register_beamoptics_push(py_Kicker); - py::class_ py_Multipole(me, "Multipole"); + py::class_ py_Multipole(me, "Multipole"); py_Multipole .def(py::init< - int const, - amrex::ParticleReal const, - amrex::ParticleReal const>(), - py::arg("multiple"), py::arg("K_normal"), py::arg("K_skew"), + int, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal + >(), + py::arg("multiple"), + py::arg("K_normal"), + py::arg("K_skew"), + py::arg("dx") = 0, + py::arg("dy") = 0, + py::arg("rotation") = 0, "A general thin multipole element." ) ; @@ -299,12 +442,20 @@ void init_elements(py::module& m) ; register_beamoptics_push(py_None); - py::class_ py_NonlinearLens(me, "NonlinearLens"); + py::class_ py_NonlinearLens(me, "NonlinearLens"); py_NonlinearLens .def(py::init< - amrex::ParticleReal const, - amrex::ParticleReal const>(), - py::arg("knll"), py::arg("cnll"), + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal + >(), + py::arg("knll"), + py::arg("cnll"), + py::arg("dx") = 0, + py::arg("dy") = 0, + py::arg("rotation") = 0, "Single short segment of the nonlinear magnetic insert element." ) ; @@ -313,7 +464,8 @@ void init_elements(py::module& m) py::class_(me, "Programmable", py::dynamic_attr()) .def(py::init< amrex::ParticleReal, - int>(), + int + >(), py::arg("ds") = 0.0, py::arg("nslice") = 1, "A programmable beam optics element." ) @@ -353,19 +505,28 @@ void init_elements(py::module& m) ) ; - py::class_ py_Quad(me, "Quad"); + py::class_ py_Quad(me, "Quad"); py_Quad .def(py::init< - amrex::ParticleReal const, - amrex::ParticleReal const, - int const>(), - py::arg("ds"), py::arg("k"), py::arg("nslice") = 1, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + int + >(), + py::arg("ds"), + py::arg("k"), + py::arg("dx") = 0, + py::arg("dy") = 0, + py::arg("rotation") = 0, + py::arg("nslice") = 1, "A Quadrupole magnet." ) ; register_beamoptics_push(py_Quad); - py::class_ py_RFCavity(me, "RFCavity"); + py::class_ py_RFCavity(me, "RFCavity"); py_RFCavity .def(py::init< amrex::ParticleReal, @@ -374,90 +535,155 @@ void init_elements(py::module& m) amrex::ParticleReal, std::vector, std::vector, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, int, int >(), - py::arg("ds"), py::arg("escale"), py::arg("freq"), - py::arg("phase"), py::arg("cos_coefficients"), py::arg("sin_coefficients"), - py::arg("mapsteps") = 1, py::arg("nslice") = 1, + py::arg("ds"), + py::arg("escale"), + py::arg("freq"), + py::arg("phase"), + py::arg("cos_coefficients"), + py::arg("sin_coefficients"), + py::arg("dx") = 0, + py::arg("dy") = 0, + py::arg("rotation") = 0, + py::arg("mapsteps") = 1, + py::arg("nslice") = 1, "An RF cavity." ) ; register_beamoptics_push(py_RFCavity); - py::class_ py_Sbend(me, "Sbend"); + py::class_ py_Sbend(me, "Sbend"); py_Sbend .def(py::init< - amrex::ParticleReal const, - amrex::ParticleReal const, - int const>(), - py::arg("ds"), py::arg("rc"), py::arg("nslice") = 1, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + int + >(), + py::arg("ds"), + py::arg("rc"), + py::arg("dx") = 0, + py::arg("dy") = 0, + py::arg("rotation") = 0, + py::arg("nslice") = 1, "An ideal sector bend." ) ; register_beamoptics_push(py_Sbend); - py::class_ py_CFbend(me, "CFbend"); + py::class_ py_CFbend(me, "CFbend"); py_CFbend .def(py::init< - amrex::ParticleReal const, - amrex::ParticleReal const, - amrex::ParticleReal const, - int const>(), - py::arg("ds"), py::arg("rc"), py::arg("k"), py::arg("nslice") = 1, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + int + >(), + py::arg("ds"), + py::arg("rc"), + py::arg("k"), + py::arg("dx") = 0, + py::arg("dy") = 0, + py::arg("rotation") = 0, + py::arg("nslice") = 1, "An ideal combined function bend (sector bend with quadrupole component)." ) ; register_beamoptics_push(py_CFbend); - py::class_ py_Buncher(me, "Buncher"); + py::class_ py_Buncher(me, "Buncher"); py_Buncher .def(py::init< - amrex::ParticleReal const, - amrex::ParticleReal const>(), - py::arg("V"), py::arg("k"), + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal + >(), + py::arg("V"), + py::arg("k"), + py::arg("dx") = 0, + py::arg("dy") = 0, + py::arg("rotation") = 0, "A short linear RF cavity element at zero-crossing for bunching." ) ; register_beamoptics_push(py_Buncher); - py::class_ py_ShortRF(me, "ShortRF"); + py::class_ py_ShortRF(me, "ShortRF"); py_ShortRF .def(py::init< - amrex::ParticleReal const, - amrex::ParticleReal const, - amrex::ParticleReal const>(), - py::arg("V"), py::arg("freq"), py::arg("phase") = -90.0, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal + >(), + py::arg("V"), + py::arg("freq"), + py::arg("phase") = -90.0, + py::arg("dx") = 0, + py::arg("dy") = 0, + py::arg("rotation") = 0, "A short RF cavity element." ) ; register_beamoptics_push(py_ShortRF); - py::class_ py_SoftSolenoid(me, "SoftSolenoid"); + py::class_ py_SoftSolenoid(me, "SoftSolenoid"); py_SoftSolenoid .def(py::init< - amrex::ParticleReal const, - amrex::ParticleReal const, + amrex::ParticleReal, + amrex::ParticleReal, std::vector, std::vector, - int const, - int const + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + int, + int >(), - py::arg("ds"), py::arg("bscale"), - py::arg("cos_coefficients"), py::arg("sin_coefficients"), - py::arg("mapsteps") = 1, py::arg("nslice") = 1, + py::arg("ds"), + py::arg("bscale"), + py::arg("cos_coefficients"), + py::arg("sin_coefficients"), + py::arg("dx") = 0, + py::arg("dy") = 0, + py::arg("rotation") = 0, + py::arg("mapsteps") = 1, + py::arg("nslice") = 1, "A soft-edge solenoid." ) ; register_beamoptics_push(py_SoftSolenoid); - py::class_ py_Sol(me, "Sol"); + py::class_ py_Sol(me, "Sol"); py_Sol .def(py::init< amrex::ParticleReal, amrex::ParticleReal, - int>(), - py::arg("ds"), py::arg("ks"), py::arg("nslice") = 1, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + int + >(), + py::arg("ds"), + py::arg("ks"), + py::arg("dx") = 0, + py::arg("dy") = 0, + py::arg("rotation") = 0, + py::arg("nslice") = 1, "An ideal hard-edge Solenoid magnet." ) ; @@ -467,37 +693,56 @@ void init_elements(py::module& m) py_PRot .def(py::init< amrex::ParticleReal, - amrex::ParticleReal>(), - py::arg("phi_in"), py::arg("phi_out"), + amrex::ParticleReal + >(), + py::arg("phi_in"), + py::arg("phi_out"), "An exact pole-face rotation in the x-z plane. Both angles are in degrees." ) ; register_beamoptics_push(py_PRot); - py::class_ py_SoftQuadrupole(me, "SoftQuadrupole"); + py::class_ py_SoftQuadrupole(me, "SoftQuadrupole"); py_SoftQuadrupole .def(py::init< amrex::ParticleReal, amrex::ParticleReal, std::vector, std::vector, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, int, int >(), - py::arg("ds"), py::arg("gscale"), - py::arg("cos_coefficients"), py::arg("sin_coefficients"), - py::arg("mapsteps") = 1, py::arg("nslice") = 1, + py::arg("ds"), + py::arg("gscale"), + py::arg("cos_coefficients"), + py::arg("sin_coefficients"), + py::arg("dx") = 0, + py::arg("dy") = 0, + py::arg("rotation") = 0, + py::arg("mapsteps") = 1, + py::arg("nslice") = 1, "A soft-edge quadrupole." ) ; register_beamoptics_push(py_SoftQuadrupole); - py::class_ py_ThinDipole(me, "ThinDipole"); + py::class_ py_ThinDipole(me, "ThinDipole"); py_ThinDipole .def(py::init< - amrex::ParticleReal const, - amrex::ParticleReal const>(), - py::arg("theta"), py::arg("rc"), + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal, + amrex::ParticleReal + >(), + py::arg("theta"), + py::arg("rc"), + py::arg("dx") = 0, + py::arg("dy") = 0, + py::arg("rotation") = 0, "A thin kick model of a dipole bend." ) ;