Phasespace#

This is an example of plotting phase space distributions. First, create a simple line, a tracker and a particle set:

Hide code cell content

%load_ext autoreload
%autoreload 2

import xtrack as xt
import xpart as xp
import xplt
import numpy as np

np.random.seed(43543557)

xplt.apply_style()

Hide code cell content

## Generate a simple 6-fold symmetric FODO lattice

n = 6  # number of sections
elements = {
    "QF": xt.Multipole(length=0.3, knl=[0, +0.63]),
    "QD": xt.Multipole(length=0.3, knl=[0, -0.48]),
    "MU": xt.Multipole(length=0.5, knl=[np.pi / n], hxl=[np.pi / n]),
}
parts = {
    "a": [
        xt.Node(0.7, "QF"),
        xt.Node(1.4, "MU"),
        xt.Node(2.1, "QD"),
        xt.Node(2.8, "MU"),
    ],
    "b": [
        xt.Node(2.2, "MU"),
        xt.Node(2.9, "QD"),
        xt.Node(3.6, "MU"),
        xt.Node(4.3, "QF"),
    ],
}
nodes = [xt.Node(5.0 * i, "a" if i % 2 else "b", name=f"S{i+1}") for i in range(n)]

line = xt.Line.from_sequence(
    nodes, length=5.0 * n, sequences=parts, elements=elements, auto_reorder=True
)
line = line.cycle(4)
line.particle_ref = xp.Particles()
line.build_tracker();

Hide code cell content

## Generate particles
nparticles = int(1e4)

# Transverse distribution (gaussian)
norm_emitt_x = 4e-6  # normalized 1-sigma emittance in m*rad (=beta*gamma*emitt_x)
norm_emitt_y = 1e-6  # normalized 1-sigma emittance in m*rad (=beta*gamma*emitt_y)
x, px = xp.generate_2D_gaussian(nparticles)
y, py = xp.generate_2D_gaussian(nparticles)

# Longitudinal distribution (coasting beam)
rel_momentum_spread = 1e-4  # relative momentum spread ( P/p0 - 1 )
zeta = line.get_length() * np.random.uniform(-0.5, 0.5, nparticles)
delta = rel_momentum_spread * xp.generate_2D_gaussian(nparticles)[0]

particles = line.build_particles(
    x_norm=x,
    px_norm=px,
    nemitt_x=norm_emitt_x,
    y_norm=y,
    py_norm=py,
    nemitt_y=norm_emitt_y,
    method="4d",  # for twiss (default is 6d, won't work without a cavity)
    zeta=zeta,
    delta=delta,
)

Default phasespace plot#

Create a default PhasespacePlot:

../_images/d395d1d49159f29d1a97c878968d84823e65d61d27bb75e15a2014d166d3df13.png

1D Histograms can also be plotted using ParticleHistogramPlot.
By default the particle count per bin is plotted, here we use kind= to plot the charge instead:

plot = xplt.ParticleHistogramPlot(
    "x", particles, kind="charge", bin_width=1e-4, figsize=(4, 3)
)  # bin_width is in data units (m)
../_images/4a1ab6269f251b27c6b5aef44bd748cef7db64c80aa8f862dc2d742adcd12b4d.png

Customisation#

Use the parameter kind to specify what is plotted as a comma separated list of pairs a-b. The properties a and b can be any property of a particle such as x, px, y, delta, zeta, Jx, Θy etc. Note that zeta is the absolute value, while zeta_wrapped is the wrapped value in range (-circumference/2 ; +circumference/2). See Default properties for a complete list of default properties and refer to Adding custom data properties for an explanation of how to add custom ones.

Tip

The kind= parameter accepts abbreviations for common pairs, e.g. x for x-px, z for zeta-delta or Y for Y-Py

Here we also add some ellipses to indicate standard deviation and percentiles, disable the default projections, and use a scatter plot with some transparency. See PhasespacePlot for details.

plot = xplt.PhaseSpacePlot(
    particles,
    mask=particles.particle_id < 1e4,
    kind="x,y,x-y",
    plot="scatter",  # using a scatter plot instead of a 2D histogram
    scatter_kwargs=dict(alpha=0.2),  # scatter plot with semi-transparent color
    projections=False,  # No projections onto axes
    display_units=dict(p="urad"),  # p as shorthand for px and py
    mean=True,  # show mean cross for all
    std=[True, None, True],  # show std ellipse for first and last
    percentiles=[[90], [70, 80, 90], None],  # show some percentile ellipses
)
plot.ax[2].set(title="Transverse profile");
../_images/50721b59369e0ace5b7fb85111bf87fbed3fe7abc9a4ef363593240f4bba3677.png

Tip

Most parameters accept a list of values to specify different values for each subplot

Normalized coordinates#

Calculate twiss parameters for normalization:

# twiss at location of particles
tw = line.twiss(method="4d").rows[np.unique(particles.at_element)]

Use uppercase letters for normalized coordinates (X is the shorthand for X-Px):

plot = xplt.PhaseSpacePlot(particles, kind="X,Y-Py", twiss=tw, std=True)
../_images/4ed4a7286016967570c530175eaa9cccb6242f53a42af536a3cd0f676d434b39.png

Color by 3rd coordinate#

plot = xplt.PhaseSpacePlot(
    particles,
    "x,y,zeta_wrapped-delta",
    color="Θx,Jy,t",  # <-- color by value
    cmap="petroff_cyclic",
    cbar_loc="inside upper right",
    twiss=tw,
    grid=False,
)
../_images/aca88884536b368deb754bae1edee26567a8277716459978a97e24866755d839.png

Monitor data#

Track the particle for a couple of turns:

line.track(particles, num_turns=100, turn_by_turn_monitor=True)

Use mask= to select a subset of the data to plot, e.g. a single turn:

plot = xplt.PhaseSpacePlot(
    line.record_last_track,
    mask=(slice(None), 83),  # select all particles and turn 83
    mean=(1, 1, 0),
    std=(1, 1, 0),
)
plot.fig.suptitle("Particle distribution for a single turn");
../_images/a62d890426f819a27ea10ae9c7c8a75ce7c90df0e49f49dff6778ac8aa674f35.png

Use masks= to set a different mask for each subplot:

plot = xplt.PhaseSpacePlot(
    line.record_last_track,
    kind="y,y",
    titles=("First turn", "Last turn"),
    sharex="all",
    sharey="all",
    masks=[
        (slice(500), 0),
        (slice(500), -1),
    ],  # select 500 particles at first and last turn
    projections="x",
    mean=True,
    std=True,
)
plot.ax[1].set(ylabel=None);
../_images/7a52df0afc50c2e4a1014f5f9f93a12928300d5ea5eb2efee9613b8fd55497de.png

Plot the trace of some particles:

plot = xplt.PhaseSpacePlot(
    line.record_last_track,
    mask=([17, 18, 21], slice(None)),  # select particles 17,18,21 and all turns
    projections=False,
)
../_images/64b0b8884134afc28181c2f076ecefa95a6e6da0607c9c0f49e2bae29523b4de.png

Plot the trace of some particles as distinct plots on the same axis:

ax = None
for particle_i in (17, 18, 21):
    plot = xplt.PhaseSpacePlot(
        line.record_last_track,
        mask=(particle_i, slice(None)),  # select particle i and all turns
        scatter_kwargs=dict(label=f"#{particle_i}"),
        kind="X",
        twiss=tw,
        titles=("Trace of single particles",),
        ax=ax,  # draw on same plot as before
    )
    ax = plot.ax

ax.legend(title="Particle");
../_images/3c4a1324d25b4184fdd6f605c8c9a014990f792c65143824591deb55d048fcdb.png

See also