Skip to main content

This notebook was created by Sergey Tomin (sergey.tomin@desy.de). May 2025.

get_envelope Function Documentation

Overview

The get_envelope function computes statistical beam properties, commonly known as Twiss parameters and envelope characteristics, from a ParticleArray. This is essential for characterizing the beam's phase-space distribution.

info

As usual, Jupyter Notebook version can be found in Ocelot Repository

The function performs several key steps:

  1. Particle Slicing (Optional): It can filter particles based on their longitudinal coordinate (τ\tau) to analyze a specific "slice" of the bunch.
  2. Dispersion Correction (Optional): It can subtract the effects of linear dispersion from particle coordinates. Dispersion values can either be provided from a design Twiss object or automatically estimated from the particle data.
  3. Coordinate Transformation: Ocelot's normalized transverse momenta (x^=px/p0\hat{x}' = p_x/p_0, y^=py/p0\hat{y}' = p_y/p_0) are converted to geometric angles (x=dx/dsx' = dx/ds, y=dy/dsy' = dy/ds) necessary for standard Twiss analysis.
  4. Moment Calculation: It calculates first-order (centroids) and second-order moments (beam sizes, divergences, and correlations) of the particle distribution.
  5. Twiss Parameter Derivation: Standard Twiss parameters (α,β\alpha, \beta), geometric emittances, and normalized emittances are computed from these moments.

If, after optional slicing, fewer than three particles remain, the function returns a default (zeroed) Twiss object and logs a warning.

Syntax

def get_envelope(p_array, tws_i=None, bounds=None, slice=None, auto_disp=False):

Parameters

  1. p_array (ParticleArray) The input particle array. The function uses the phase-space coordinates x,x^,y,y^,τ,pocelotx, \hat{x}', y, \hat{y}', \tau, p_{\text{ocelot}} for each particle, where:

    • x,yx, y: transverse positions.
    • x^(=px/p0),y^(=py/p0)\hat{x}' (= p_x/p_0), \hat{y}' (= p_y/p_0): Ocelot's normalized transverse momenta.
    • τ\tau: longitudinal coordinate.
    • pocelot(=(EE0)/(p0c))p_{\text{ocelot}} (= (E-E_0)/(p_0 c)): Ocelot's relative energy deviation. For clarity in the formulas within this document, this quantity will be denoted as δ\delta. The code internally uses the variable p for this quantity.
  2. tws_i (Twiss, optional) A reference Twiss object providing design dispersion values (Dx,Dx,Dy,DyD_x, D'_x, D_y, D'_y). Note DxD'_x is the dispersive term for the angle xx'. If provided, these values are used for dispersion correction. Default is None.

  3. bounds (list, optional) A two-element list [left_bound, right_bound] defining a longitudinal slice for analysis, in units of στ\sigma_\tau (the standard deviation of τ\tau for the full beam).

    • If provided, particles are filtered to keep only those within the range: z0+left_boundσττiz0+right_boundστz_0 + \text{left\_bound} \cdot \sigma_{\tau} \le \tau_i \le z_0 + \text{right\_bound} \cdot \sigma_{\tau} where z0z_0 is a reference longitudinal position determined by the slice parameter.
    • If None (default), all particles are used.
  4. slice (str or None, optional) Determines the reference longitudinal position z0z_0 when bounds are active:

    • None (default): z0=τz_0 = \langle \tau \rangle (the mean of τ\tau for all particles).
    • "Imax": z0z_0 is set to the τ\tau position where the beam current (longitudinal density) is maximal.
  5. auto_disp (bool, optional) Controls automatic dispersion estimation:

    • If True AND tws_i is None, linear dispersion (Dx,Dx,Dy,DyD_x, D'_x, D_y, D'_y) is estimated directly from the statistical moments of the input p_array.
    • If False (default) or if tws_i is provided, no automatic estimation is performed.

Core Operations and Calculations

1. Particle Slicing (Filtering)

If bounds are specified, the particle data (x,x^,y,y^,τ,δx, \hat{x}', y, \hat{y}', \tau, \delta) is first filtered to include only particles within the defined longitudinal slice, as described under the bounds and slice parameters. (Recall δ\delta is used here for Ocelot's relative energy deviation pocelotp_{\text{ocelot}}).

2. Dispersion Correction

Dispersion describes the coupling between particle energy deviation and transverse position/angle. This correction aims to analyze the "betatron" motion, which is the motion independent of energy effects.

  • Source of Dispersion Values:

    • If tws_i is provided, its dispersion values (Dx,Dx,Dy,DyD_x, D'_x, D_y, D'_y) are used.
    • If tws_i is None and auto_disp is True, dispersion values are estimated from the particle distribution: Dx=(xxˉ)(δδˉ)(δδˉ)2,Dx=(x^x^ˉ)(δδˉ)(δδˉ)2D_x = \frac{\langle (x-\bar{x})(\delta-\bar{\delta}) \rangle}{\langle (\delta-\bar{\delta})^2 \rangle}, \quad D'_x = \frac{\langle (\hat{x}'-\bar{\hat{x}'})(\delta-\bar{\delta}) \rangle}{\langle (\delta-\bar{\delta})^2 \rangle} and similarly for DyD_y and DyD'_y. (Here, xˉ=x\bar{x} = \langle x \rangle, δˉ=δ\bar{\delta} = \langle \delta \rangle, etc.)
  • Applying the Correction: The particle coordinates are then corrected by subtracting the dispersive contribution. For each particle ii:

    xixiDxδix^ix^iDxδiyiyiDyδiy^iy^iDyδi\begin{aligned} x_i &\leftarrow x_i - D_x \cdot \delta_i \\ \hat{x}'_i &\leftarrow \hat{x}'_i - D'_x \cdot \delta_i \\ y_i &\leftarrow y_i - D_y \cdot \delta_i \\ \hat{y}'_i &\leftarrow \hat{y}'_i - D'_y \cdot \delta_i \end{aligned}

    These dispersion-corrected coordinates are used for subsequent moment calculations.

3. Transformation to Geometric Angles

Twiss parameters are conventionally defined using geometric angles (x=dx/dsx' = dx/ds). Ocelot stores normalized transverse momenta (x^=px/p0\hat{x}' = p_x/p_0). A transformation is needed.

  • Definitions and Key Relations:

    • Ocelot normalized transverse momentum: x^=px/p0\hat{x}' = p_x/p_0.
    • Ocelot relative energy deviation (denoted as δ\delta in this document): δ=ΔEp0c=EE0p0c\delta = \frac{\Delta E}{p_0 c} = \frac{E-E_0}{p_0c} where EE and E0E_0 are the total energies of the particle and reference particle, respectively, and p0p_0 is the total momentum of the reference particle.
    • Geometric angle: x=px/psx' = p_x/p_s, where ps=p2px2py2p_s = \sqrt{p^2 - p_x^2 - p_y^2} is the longitudinal momentum of the particle, and pp is its total momentum.
  • Relating Particle Total Momentum (pp) to Reference Momentum (p0p_0) and δ\delta:

    The relativistic energy-momentum relation is E2=(pc)2+(m0c2)2E^2 = (pc)^2 + (m_0c^2)^2. For the reference particle: E02=(p0c)2+(m0c2)2E_0^2 = (p_0c)^2 + (m_0c^2)^2. For an arbitrary particle: E=E0+δp0cE = E_0 + \delta p_0 c. Substituting this into the particle's energy-momentum relation: (pc)2=(E0+δp0c)2(m0c2)2=E02+2E0δp0c+(δp0c)2(m0c2)2(pc)^2 = (E_0 + \delta p_0 c)^2 - (m_0c^2)^2 = E_0^2 + 2E_0 \delta p_0 c + (\delta p_0 c)^2 - (m_0c^2)^2 Using E02(m0c2)2=(p0c)2E_0^2 - (m_0c^2)^2 = (p_0c)^2: (pc)2=(p0c)2+2E0δp0c+(δp0c)2(pc)^2 = (p_0c)^2 + 2E_0 \delta p_0 c + (\delta p_0 c)^2 Dividing by (p0c)2(p_0c)^2: (pp0)2=1+2E0δp0c+δ2\left(\frac{p}{p_0}\right)^2 = 1 + \frac{2E_0 \delta}{p_0 c} + \delta^2 Since E0/(p0c)=1/β0E_0/(p_0c) = 1/\beta_0. Thus: (pp0)2=1+2δβ0+δ2\left(\frac{p}{p_0}\right)^2 = 1 + \frac{2\delta}{\beta_0} + \delta^2

  • Exact Relation for Geometric Angle:

    Substituting px=x^p0p_x = \hat{x}'p_0, py=y^p0p_y = \hat{y}'p_0 and the expression for (p/p0)2(p/p_0)^2 into x=px/p2px2py2x' = p_x/\sqrt{p^2 - p_x^2 - p_y^2}:

    x=x^(pp0)2(x^2+y^2)=x^1+2δβ0+δ2(x^2+y^2)x' = \frac{\hat{x}'}{\sqrt{\left(\frac{p}{p_0}\right)^2 - (\hat{x}'^2 + \hat{y}'^2)}} = \frac{\hat{x}'}{\sqrt{1 + \frac{2\delta}{\beta_0} + \delta^2 - (\hat{x}'^2 + \hat{y}'^2)}}
  • Approximation Used in get_envelope:

    The square root term (1+Z)1/2(1+Z)^{-1/2} is approximated using the first-order Taylor expansion 1Z/21 - Z/2. Let Z=2δβ0+δ2(x^2+y^2)Z = \frac{2\delta}{\beta_0} + \delta^2 - (\hat{x}'^2 + \hat{y}'^2). Then the correction factor is approximated as: C112Z=112(2δβ0+δ2(x^2+y^2))=1δβ012δ2+12(x^2+y^2)C \approx 1 - \frac{1}{2}Z = 1 - \frac{1}{2}\left(\frac{2\delta}{\beta_0} + \delta^2 - (\hat{x}'^2 + \hat{y}'^2)\right) = 1 - \frac{\delta}{\beta_0} - \frac{1}{2}\delta^2 + \frac{1}{2}(\hat{x}'^2 + \hat{y}'^2) The code further assumes an ultra-relativistic beam, where β01\beta_0 \approx 1. Thus, the implemented transformation (where xx' now denotes the geometric angle) becomes: xx^(1δ12δ2+12x^2+12y^2)x' \approx \hat{x}' \left(1 - \delta - \frac{1}{2}\delta^2 + \frac{1}{2}\hat{x}'^2 + \frac{1}{2}\hat{y}'^2\right) And similarly for yy'. The variables used in the code px, py, and p (Ocelot's pocelotp_{\text{ocelot}}) correspond to x^\hat{x}', y^\hat{y}', and δ\delta respectively in this formula. This approximation effectively keeps terms up to second order in δ,x^,y^\delta, \hat{x}', \hat{y}' within the multiplicative correction factor.

4. Calculation of Moments and Twiss Parameters

After the above corrections and transformations, the following are computed:

  • Centroids (First-Order Moments):

    These are the mean values of the particle coordinates: x,x,y,y,τ,δ\langle x \rangle, \langle x' \rangle, \langle y \rangle, \langle y' \rangle, \langle \tau \rangle, \langle \delta \rangle. Note: x\langle x' \rangle and y\langle y' \rangle are centroids of the geometric angles. δ\langle \delta \rangle (mean relative energy deviation) is stored as tws.p.

  • Covariances (Second-Order Moments):

    These are variances and covariances of the particle coordinates, taken with respect to their means (i.e., they are central moments). For example, the variance of xx is (xx)2\langle (x - \langle x \rangle)^2 \rangle. The Twiss object stores them as:

    tws.xx = <(x - <x>)^2>
    tws.xpx = <(x - <x>)(x' - <x'>)>
    tws.pxpx = <(x' - <x'>)^2>
    // ... and similarly for y, y', τ, p_ocelot (δ), and cross-terms
    tws.yy = <(y - <y>)^2>
    tws.ypy = <(y - <y>)(y' - <y'>)>
    tws.pypy = <(y' - <y'>)^2>
    tws.tautau = <(τ - <τ>)^2>
    tws.pp = <(δ - <δ>)^2>
    // Cross-plane moments
    tws.xy = <(x - <x>)(y - <y>)>
    tws.xpy = <(x - <x>)(y' - <y'>)>
    tws.ypx = <(y - <y>)(x' - <x'>)>
    tws.pxpy = <(x' - <x'>)(y' - <y'>)>
  • Emittances:

    • Geometric emittances (calculated from central moments): εx=(xx)2(xx)2(xx)(xx)2=tws.xxtws.pxpxtws.xpx2\varepsilon_x = \sqrt{\langle (x - \langle x \rangle)^2 \rangle \langle (x' - \langle x' \rangle)^2 \rangle - \langle (x - \langle x \rangle)(x' - \langle x' \rangle) \rangle^2} = \sqrt{\text{tws.xx} \cdot \text{tws.pxpx} - \text{tws.xpx}^2} εy=(yy)2(yy)2(yy)(yy)2=tws.yytws.pypytws.ypy2\varepsilon_y = \sqrt{\langle (y - \langle y \rangle)^2 \rangle \langle (y' - \langle y' \rangle)^2 \rangle - \langle (y - \langle y \rangle)(y' - \langle y' \rangle) \rangle^2} = \sqrt{\text{tws.yy} \cdot \text{tws.pypy} - \text{tws.ypy}^2}
    • Normalized emittances: εnx=β0γ0εx,εny=β0γ0εy\varepsilon_{nx} = \beta_0 \gamma_0 \varepsilon_x, \quad \varepsilon_{ny} = \beta_0 \gamma_0 \varepsilon_y where β0,γ0\beta_0, \gamma_0 are relativistic factors of the reference particle (obtained from p_array.E).
  • Twiss Parameters:

    βx=tws.xxεx,αx=tws.xpxεxβy=tws.yyεy,αy=tws.ypyεy\begin{aligned} \beta_x &= \frac{\text{tws.xx}}{\varepsilon_x}, &\quad \alpha_x &= -\frac{\text{tws.xpx}}{\varepsilon_x} \\ \beta_y &= \frac{\text{tws.yy}}{\varepsilon_y}, &\quad \alpha_y &= -\frac{\text{tws.ypy}}{\varepsilon_y} \end{aligned}

    The parameters γx=(1+αx2)/βx\gamma_x = (1+\alpha_x^2)/\beta_x and γy=(1+αy2)/βy\gamma_y = (1+\alpha_y^2)/\beta_y are calculated in Twiss class internally.

  • Eigen-Emittances (eigemit_1, eigemit_2): These are calculated from the 4D transverse covariance matrix Σx,x,y,y\Sigma_{x,x',y,y'} to characterize beams with x-y coupling, representing the emittances in the decoupled eigenmodes.

Returns

Twiss

An instance of the Twiss class, populated with all the calculated centroids, moments, emittances, and Twiss parameters for the (optionally filtered and corrected) particle ensemble. Includes:

  • E: Reference energy E0E_0.
  • q: Total charge of the selected particles.
  • p: Mean relative energy deviation δ\langle \delta \rangle (corresponding to pocelot\langle p_{\text{ocelot}} \rangle).
  • Centroids: x, px, y, py, tau. (px, py are centroids of geometric angles).
  • Second-order moments: xx, xpx, pxpx, yy, ypy, pypy, tautau, pp, xy, xpy, ypx, pxpy.
  • Emittances: emit_x, emit_y, emit_xn, emit_yn, eigemit_1, eigemit_2.
  • Twiss functions: beta_x, alpha_x, beta_y, alpha_y.
  • Dispersion (if calculated or passed): Dx, Dxp (as DxD'_x) , Dy, Dyp (as DyD'_y).

Examples of Use

The following examples illustrate how to use the get_envelope function directly and how its functionality is integrated within particle tracking simulations.

Simplest Example: Calculating Twiss from a ParticleArray

First, let's demonstrate the direct use of get_envelope. We will:

  1. Define an initial set of Twiss parameters.
  2. Generate a ParticleArray distribution matched to these Twiss parameters.
  3. Use get_envelope to calculate the Twiss parameters back from the generated ParticleArray.

Ideally, the calculated Twiss parameters should closely match the initial ones.

NOTE: The output will show the Twiss parameters computed by get_envelope. Due to the statistical nature of particle generation and the finite number of particles, these will be very close but not exactly identical to tws0.

from ocelot import *
from ocelot.gui import *

# 1. Define arbitrary initial Twiss parameters
tws0 = Twiss(emit_xn=0.5e-6, emit_yn=0.5e-6, beta_x=10, beta_y=7, E=0.1)

# 2. Generate a ParticleArray matched to these Twiss parameters
# The generate_parray function aims to create a distribution whose statistical
# properties correspond to tws0. A chirp is added for later demonstration.
parray = generate_parray(tws=tws0, charge=250e-12, chirp=0.02)

# 3. Calculate Twiss parameters from the ParticleArray
# This is a direct call to get_envelope.
calculated_tws = get_envelope(parray)
print(calculated_tws)

    initializing ocelot...

emit_x = 2.5534086876131525e-09
emit_y = 2.547930016365793e-09
emit_xn = 4.996831723396634e-07
emit_yn = 4.986110369457615e-07
beta_x = 10.000263656786013
beta_y = 7.000081949297738
alpha_x = 5.5915982949718466e-06
alpha_y = 2.637941201691814e-05
Dx = 0.0
Dy = 0.0
Dxp = 0.0
Dyp = 0.0
mux = 0.0
muy = 0.0
nu_x = 0.0
nu_y = 0.0
E = 0.1
s = 0.0

Integration with track() function

The get_envelope function is automatically called within the track() function if calc_tws=True (which is the default). This allows for monitoring the beam envelope evolution along a beamline. Several arguments in track() are passed directly to get_envelope:

# Relevant arguments for get_envelope within track():
tws_track, parray_final = track(
lattice, # MagneticLattice
p_array_initial, # ParticleArray
navi=None, # Navigator
calc_tws=True, # If True (default), get_envelope() is called at each step.
bounds=None, # Passed to get_envelope for slicing (width).
slice=None, # Passed to get_envelope for slicing (position, e.g., "Imax").
twiss_disp_correction=False # Passed to get_envelope for dispersion correction.
)

Let's create a simple lattice and track a beam through it, observing how get_envelope provides the evolving Twiss parameters.

Building a Sample Lattice

We will construct a lattice containing drifts, quadrupoles, and a four-bend chicane. Then, we'll calculate the design (ideal) Twiss parameters for this lattice based on our initial tws0.

d = Drift(l=1)
qf = Quadrupole(l=0.2, k1=1)
qd = Quadrupole(l=0.2, k1=-1)

# Chicane using rectangular dipoles
alpha_bend = 5 * np.pi / 180 # rad (corrected variable name)
b1 = Bend(l=0.2, angle=-alpha_bend, e2=-alpha_bend)
b2 = Bend(l=0.2, angle=alpha_bend, e1=alpha_bend)
b3 = Bend(l=0.2, angle=alpha_bend, e2=alpha_bend)
b4 = Bend(l=0.2, angle=-alpha_bend, e1=alpha_bend)

cell = [d, qf, d, qd, d, b1, d, b2, d, b3, d, b4, d, qf, d, qd, d]
lat = MagneticLattice(cell)

# Calculate design Twiss parameters for this lattice
tws_design = twiss(lat, tws0=tws0)

# Plot the design optical functions, including dispersion Dx
plot_opt_func(lat, tws_design, top_plot=["Dx"])
plt.show()

# The chicane will introduce longitudinal dispersion (R56)
_, R, _ = lat.transfer_maps(energy=tws0.E)
print(f"Calculated R56 of the lattice = {R[4, 5]*1e3:.4f} mm")

png

    Calculated R56 of the lattice = -17.6286 mm

The plot shows the design β\beta-functions and, importantly, the horizontal dispersion DxD_x generated by the chicane.

Tracking a ParticleArray: The Role of Dispersion Correction

Now, we will track the ParticleArray (generated earlier) through this lattice.

Scenario 1: Tracking without Dispersion Correction

By default, track() calls get_envelope without enabling its internal dispersion correction (unless twiss_disp_correction=True is set, or design dispersion is passed via tws_i to get_envelope). In regions with dispersion, the calculated transverse beam sizes and emittances will be inflated by the energy spread of the beam coupled through the dispersion function. This means the Twiss parameters calculated by get_envelope will reflect the projected phase space, not the purely betatronic motion.

parray1 = parray.copy() # Use a copy of the original particle array
tws_track_no_corr, _ = track(lat, p_array=parray1, calc_tws=True) # twiss_disp_correction defaults to False

# Plot the Twiss parameters calculated by get_envelope during tracking
plot_opt_func(lat, tws_track_no_corr, top_plot=["Dx"])
plt.show()

z = 10.6 / 10.6. Applied: .6. Applied:

png

Observe in the resulting plot that DxD_x (calculated from the particle statistics by get_envelope) is zero. This is because get_envelope (without auto_disp=True or tws_i) is not aware of the lattice dispersion. The calculated βx\beta_x might also deviate significantly from the design tws_design.beta_x in dispersive regions, as the beam size includes the dispersive contribution σx2=ϵxβx+(Dxσδ)2\sigma_x^2 = \epsilon_x \beta_x + (D_x \sigma_\delta)^2.

Scenario 2: Tracking with Dispersion Correction Enabled

To obtain Twiss parameters that better reflect the betatronic motion and match the design optics (especially emittance and β\beta-functions in dispersive regions), we need to enable dispersion correction within get_envelope. When using track(), this is done by setting twiss_disp_correction=True. This will make get_envelope automatically estimate and subtract the linear dispersion from the particle statistics (auto_disp=True behavior for get_envelope).

parray2 = parray.copy() # Use another copy
tws_track_with_corr, _ = track(lat, p_array=parray2, calc_tws=True, twiss_disp_correction=True)

# Plot the Twiss parameters calculated by get_envelope during tracking
plot_opt_func(lat, tws_track_with_corr, top_plot=["Dx"])
plt.show()

z = 10.6 / 10.6. Applied: .6. Applied:

png

With twiss_disp_correction=True, the DxD_x calculated by get_envelope from the particle statistics should now closely follow the design dispersion. The betatronic βx\beta_x and emittance εx\varepsilon_x will also be more representative of the intrinsic beam properties.

Observing Bunch Compression

Our lattice includes a chicane, which has an R56R_{56} (longitudinal dispersion). If the incoming beam has an energy chirp (correlation between energy and longitudinal position), the chicane can compress or stretch the bunch. The parray was generated with a chirp. We can inspect the evolution of στ\sigma_\tau (RMS bunch length), which is available from the tautau attribute of the Twiss objects returned by get_envelope at each step.

# Use tws_track_with_corr from the dispersion-corrected tracking
s_coords = np.array([tw.s for tw in tws_track_with_corr])
sigma_tau_values = np.array([np.sqrt(tw.tautau) for tw in tws_track_with_corr])

plt.figure() # Ensure a new figure for clarity
plt.plot(s_coords, sigma_tau_values * 1e3) # Convert sigma_tau to mm
plt.xlabel("s [m]")
plt.ylabel(r"RMS Bunch Length $\sigma_{\tau}$ [mm]")
plt.title("Bunch Length Evolution Through the Lattice")
plt.grid(True)
plt.show()

png

The plot of στ\sigma_\tau vs. ss should show a change in bunch length as the beam passes through the chicane, demonstrating the effect of R56R_{56} on a chirped beam. This highlights how get_envelope provides crucial second-order moments for analyzing various beam dynamics effects.