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.
As usual, Jupyter Notebook version can be found in Ocelot Repository
The function performs several key steps:
- Particle Slicing (Optional): It can filter particles based on their longitudinal coordinate () to analyze a specific "slice" of the bunch.
- 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. - Coordinate Transformation: Ocelot's normalized transverse momenta (, ) are converted to geometric angles (, ) necessary for standard Twiss analysis.
- Moment Calculation: It calculates first-order (centroids) and second-order moments (beam sizes, divergences, and correlations) of the particle distribution.
- Twiss Parameter Derivation: Standard Twiss parameters (), 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
-
p_array
(ParticleArray
) The input particle array. The function uses the phase-space coordinates for each particle, where:- : transverse positions.
- : Ocelot's normalized transverse momenta.
- : longitudinal coordinate.
- : Ocelot's relative energy deviation. For clarity in the formulas within this document, this quantity will be denoted as . The code internally uses the variable
p
for this quantity.
-
tws_i
(Twiss
, optional) A referenceTwiss
object providing design dispersion values (). Note is the dispersive term for the angle . If provided, these values are used for dispersion correction. Default isNone
. -
bounds
(list
, optional) A two-element list[left_bound, right_bound]
defining a longitudinal slice for analysis, in units of (the standard deviation of for the full beam).- If provided, particles are filtered to keep only those within the range:
where is a reference longitudinal position determined by the
slice
parameter. - If
None
(default), all particles are used.
- If provided, particles are filtered to keep only those within the range:
where is a reference longitudinal position determined by the
-
slice
(str
orNone
, optional) Determines the reference longitudinal position whenbounds
are active:None
(default): (the mean of for all particles)."Imax"
: is set to the position where the beam current (longitudinal density) is maximal.
-
auto_disp
(bool
, optional) Controls automatic dispersion estimation:- If
True
ANDtws_i
isNone
, linear dispersion () is estimated directly from the statistical moments of the inputp_array
. - If
False
(default) or iftws_i
is provided, no automatic estimation is performed.
- If
Core Operations and Calculations
1. Particle Slicing (Filtering)
If bounds
are specified, the particle data () is first filtered to include only particles within the defined longitudinal slice, as described under the bounds
and slice
parameters. (Recall is used here for Ocelot's relative energy deviation ).
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 () are used. - If
tws_i
isNone
andauto_disp
isTrue
, dispersion values are estimated from the particle distribution: and similarly for and . (Here, , , etc.)
- If
-
Applying the Correction: The particle coordinates are then corrected by subtracting the dispersive contribution. For each particle :
These dispersion-corrected coordinates are used for subsequent moment calculations.
3. Transformation to Geometric Angles
Twiss parameters are conventionally defined using geometric angles (). Ocelot stores normalized transverse momenta (). A transformation is needed.
-
Definitions and Key Relations:
- Ocelot normalized transverse momentum: .
- Ocelot relative energy deviation (denoted as in this document): where and are the total energies of the particle and reference particle, respectively, and is the total momentum of the reference particle.
- Geometric angle: , where is the longitudinal momentum of the particle, and is its total momentum.
-
Relating Particle Total Momentum () to Reference Momentum () and :
The relativistic energy-momentum relation is . For the reference particle: . For an arbitrary particle: . Substituting this into the particle's energy-momentum relation: Using : Dividing by : Since . Thus:
-
Exact Relation for Geometric Angle:
Substituting , and the expression for into :
-
Approximation Used in
get_envelope
:The square root term is approximated using the first-order Taylor expansion . Let . Then the correction factor is approximated as: The code further assumes an ultra-relativistic beam, where . Thus, the implemented transformation (where now denotes the geometric angle) becomes: And similarly for . The variables used in the code
px
,py
, andp
(Ocelot's ) correspond to , , and respectively in this formula. This approximation effectively keeps terms up to second order in 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: . Note: and are centroids of the geometric angles. (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 is . 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):
- Normalized emittances:
where are relativistic factors of the reference particle (obtained from
p_array.E
).
-
Twiss Parameters:
The parameters and are calculated in
Twiss
class internally. -
Eigen-Emittances (
eigemit_1
,eigemit_2
): These are calculated from the 4D transverse covariance matrix to characterize beams with x-y coupling, representing the emittances in the decoupled eigenmodes.
Returns
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 .q
: Total charge of the selected particles.p
: Mean relative energy deviation (corresponding to ).- 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 ) , Dy, Dyp (as ).
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:
- Define an initial set of Twiss parameters.
- Generate a
ParticleArray
distribution matched to these Twiss parameters. - Use
get_envelope
to calculate the Twiss parameters back from the generatedParticleArray
.
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")
Calculated R56 of the lattice = -17.6286 mm
The plot shows the design -functions and, importantly, the horizontal dispersion 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:
Observe in the resulting plot that (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 might also deviate significantly from the design tws_design.beta_x
in dispersive regions, as the beam size includes the dispersive contribution .
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 -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:
With twiss_disp_correction=True
, the calculated by get_envelope
from the particle statistics should now closely follow the design dispersion. The betatronic and emittance will also be more representative of the intrinsic beam properties.
Observing Bunch Compression
Our lattice includes a chicane, which has an (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 (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()
The plot of vs. should show a change in bunch length as the beam passes through the chicane, demonstrating the effect of on a chirped beam. This highlights how get_envelope
provides crucial second-order moments for analyzing various beam dynamics effects.