Skip to content

Particles

ParticleGroup

ParticleGroup(h5=None, data=None)

Particle Group class

Initialized on on openPMD beamphysics particle group:

  • h5: open h5 handle, or str that is a file
  • data: raw data

The required bunch data is stored in .data with keys

  • np.array: x, px, y, py, z, pz, t, status, weight
  • str: species

where:

  • x, y, z are positions in units of [m]
  • px, py, pz are momenta in units of [eV/c]
  • t is time in [s]
  • weight is the macro-charge weight in [C], used for all statistical calulations.
  • species is a proper species name: 'electron', etc.

Optional data:

  • np.array: id

where id is a list of unique integers that identify the particles.

Derived data can be computed as attributes:

  • .gamma, .beta, .beta_x, .beta_y, .beta_z: relativistic factors [1].
  • .r, .theta: cylidrical coordinates [m], [1]
  • .pr, .ptheta: momenta in the radial and angular coordinate directions [eV/c]
  • .Lz: angular momentum about the z axis [m*eV/c]
  • .energy : total energy [eV]
  • .kinetic_energy: total energy - mc^2 in [eV].
  • .higher_order_energy: total energy with quadratic fit in z or t subtracted [eV]
  • .p: total momentum in [eV/c]
  • .mass: rest mass in [eV]
  • .xp, .yp: Slopes \(x' = dx/dz = p_x/p_z\) and \(y' = dy/dz = p_y/p_z\) [1].

Normalized transvere coordinates can also be calculated as attributes:

  • .x_bar, .px_bar, .y_bar, .py_bar in [sqrt(m)]

The normalization is automatically calculated from the covariance matrix. See functions in .statistics for more advanced usage.

Their cooresponding amplitudes are:

.Jx, .Jy [m]

where Jx = (x_bar^2 + px_bar^2 )/2.

The momenta are normalized by the mass, so that <Jx> = norm_emit_x and similar for y.

Statistics of any of these are calculated with:

  • .min(X)
  • .max(X)
  • .ptp(X)
  • .avg(X)
  • .std(X)
  • .cov(X, Y, ...)
  • .histogramdd(X, Y, ..., bins=10, range=None)

with a string X as the name any of the properties above.

Useful beam physics quantities are given as attributes:

  • .norm_emit_x
  • .norm_emit_y
  • .norm_emit_4d
  • .higher_order_energy_spread
  • .average_current

Twiss parameters, including dispersion, for the \(x\) or \(y\) plane:

  • .twiss(plane='x', fraction=0.95, p0C=None)

For convenience, plane='xy' will calculate twiss for both planes.

Twiss matched particles, using a simple linear transformation:

  • .twiss_match(self, beta=None, alpha=None, plane='x', p0c=None, inplace=False)

The weight is required and must sum to > 0. The sum of the weights is in .charge. This can also be set: .charge = 1.234 # C, will rescale the .weight array

All attributes can be accessed with brackets: [key]

Additional keys are allowed for convenience: ['min_prop'] will return .min('prop') ['max_prop'] will return .max('prop') ['ptp_prop'] will return .ptp('prop') ['mean_prop'] will return .avg('prop') ['sigma_prop'] will return .std('prop') ['cov_prop1__prop2'] will return .cov('prop1', 'prop2')[0,1]

Units for all attributes can be accessed by:

  • .units(key)

Particles are often stored at the same time (i.e. from a t-based code), or with the same z position (i.e. from an s-based code.) Routines:

  • drift_to_z(z0)
  • drift_to_t(t0)

help to convert these. If no argument is given, particles will be drifted to the mean. Related properties are:

  • .in_t_coordinates returns True if all particles have the same \(t\) corrdinate
  • .in_z_coordinates returns True if all particles have the same \(z\) corrdinate

Convenient plotting is provided with:

  • .plot(...)
  • .slice_plot(...)

Use help(ParticleGroup.plot), etc. for usage.

Parameters:

  • h5 (str, pathlib.Path, h5py.File, or dict, default: None ) –

    Source of particle data. Can be a filename (str or Path) to an HDF5 file, an open h5py.File handle, or a dict-like object with openPMD particle data. Mutually exclusive with data.

  • data (dict, default: None ) –

    Raw particle data as a dict with keys 'x', 'px', 'y', 'py', 'z', 'pz', 't', 'status', 'weight', and 'species'. Mutually exclusive with h5.

Raises:

  • NotImplementedError

    If both h5 and data are provided.

Methods:

  • __add__

    Overloads the + operator to join particle groups.

  • __contains__

    Checks internal data

  • __eq__

    Check equality of species and internal data

  • __getitem__

    Returns a property or statistical quantity that can be computed:

  • __len__

    Return the number of particles.

  • __repr__

    Return a detailed string representation with memory location.

  • __str__

    Return a human-readable summary string.

  • apply_wakefield

    Apply wakefield momentum kicks to this ParticleGroup.

  • assign_id

    Assign unique integer IDs to all particles.

  • avg

    Return the weighted average of a particle property.

  • bunching

    Calculate the normalized bunching parameter, which is the magnitude of the

  • copy

    Return a deep copy of this ParticleGroup.

  • cov

    Covariance matrix from any properties

  • delta

    Return an attribute array relative to its weighted mean.

  • drift

    Drift all particles for a given time interval.

  • drift_to_t

    Drifts all particles to the same t.

  • drift_to_z

    Drift all particles to the same z position.

  • fractional_split

    Split particles based on a given array key and a list of specified fractions or a single fraction.

  • from_bmad

    Convert Bmad particle data to a ParticleGroup.

  • from_genesis4

    Create a ParticleGroup from a Genesis4 .par HDF5 file.

  • higher_order_energy_calc

    Fits a polynmial with order order to the Energy vs. time, , and returns the energy with this subtracted.

  • histogramdd

    Wrapper for numpy.histogramdd, but accepts property names as keys.

  • info

    Return information about a specific statistics key.

  • linear_point_transform

    Applies a linear transformation to the particle's spatial coordinates and

  • max

    Return the maximum value of a particle property.

  • min

    Return the minimum value of a particle property.

  • plot

    1d or 2d density plot.

  • ptp

    Return the peak-to-peak (max - min) range of a particle property.

  • resample

    Resample particles randomly.

  • rotate

    Rotate the beam inplace by the specified angles first around the z axis, then the x axis, finally around the y axis.

  • rotate_x

    Rotate the object about the x-axis by angle theta (radians).

  • rotate_y

    Rotate the object about the y-axis by angle theta (radians).

  • rotate_z

    Rotate the object about the z-axis by angle theta (radians).

  • slice_plot

    Plot slice statistics along the bunch.

  • slice_statistics

    Compute slice statistics along the bunch.

  • split

    Split the particle group into even chunks sorted by a key.

  • std

    Return the weighted standard deviation of a particle property.

  • to_bmad

    Convert to Bmad phase space coordinates.

  • twiss

    Returns Twiss and Dispersion dict.

  • twiss_match

    Returns a ParticleGroup with requested Twiss parameters.

  • units

    Get the unit information (pmd_unit) for a given key.

  • wakefield_plot

    Plot per-particle wakefield kicks overlaid with the bunch density.

  • where

    Filter particles by a boolean condition.

  • write

    Write particle data to an HDF5 file or group in openPMD format.

  • write_astra

    Write Astra style particles.

  • write_bmad

    Write particle data in Bmad format.

  • write_elegant

    Write particle data in Elegant SDDS format.

  • write_genesis2_beam_file

    Write particle data as a Genesis 1.3 v2 beam file.

  • write_genesis4_beam

    Write particle data as a Genesis 1.3 v4 beam file.

  • write_genesis4_distribution

    Write particle data as a Genesis 1.3 v4 distribution HDF5 file.

  • write_gpt

    Write particle data in GPT (General Particle Tracer) format.

  • write_impact

    Write particle data in Impact-T format.

  • write_litrack

    Write particle data in LiTrack format.

  • write_lucretia

    Write particle data in Lucretia format.

  • write_opal

    Write particle data in OPAL (Object Oriented Parallel Accelerator Library) format.

  • write_simion

    Write particle data in SIMION format.

Attributes:

  • Jx

    Normalized amplitude J in the x-px plane

  • Jy

    Normalized amplitude J in the y-py plane

  • Lz

    Angular momentum around the z axis in m*eV/c

  • average_current

    Simple average current = charge / dt in [A], with dt = (max_t - min_t)

  • beta

    Relativistic beta

  • beta_x

    Relativistic beta, x component

  • beta_y

    Relativistic beta, y component

  • beta_z

    Relativistic beta, z component

  • charge

    Total charge in C

  • data

    Internal data dict

  • energy

    Total energy in eV

  • gamma

    Relativistic gamma

  • higher_order_energy

    Fits a quadratic (order=2) to the Energy vs. time, and returns the energy with this subtracted.

  • higher_order_energy_spread

    Legacy syntax to compute the standard deviation of higher_order_energy.

  • id

    id integer

  • in_t_coordinates

    Returns True if all particles have the same t coordinate

  • in_z_coordinates

    Returns True if all particles have the same z coordinate

  • kinetic_energy

    Kinetic energy in eV

  • mass

    Rest mass in eV

  • n_alive

    Number of alive particles, defined by status == 1

  • n_dead

    Number of dead particles, defined by status != 1

  • n_particle

    Total number of particles. Same as len

  • norm_emit_4d

    Normalized emittance in the xy planes (4D)

  • norm_emit_x

    Normalized emittance in the x plane

  • norm_emit_y

    Normalized emittance in the y plane

  • p

    Total momemtum in eV/c

  • pr

    Momentum in the radial direction in eV/c

  • ptheta

    Momentum in the polar theta direction.

  • px

    px coordinate in [eV/c]

  • px_bar

    Normalized px in units of sqrt(m)

  • py

    py coordinate in [eV/c]

  • py_bar

    Normalized py in units of sqrt(m)

  • pz

    pz coordinate in [eV/c]

  • r

    Radius in the xy plane: r = sqrt(x^2 + y^2) in m

  • species

    species string

  • species_charge

    Species charge in C

  • status

    status coordinate in [1]

  • t

    t coordinate in [s]

  • theta

    Angle in xy plane: theta = arctan2(y, x) in radians

  • weight

    weight coordinate in [C]

  • x

    x coordinate in [m]

  • x_bar

    Normalized x in units of sqrt(m)

  • xp

    x slope px/pz (dimensionless)

  • y

    y coordinate in [m]

  • y_bar

    Normalized y in units of sqrt(m)

  • yp

    y slope py/pz (dimensionless)

  • z

    z coordinate in [m]

Source code in beamphysics/particles.py
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
def __init__(self, h5=None, data=None):
    if h5 and data:
        # TODO:
        # Allow merging or changing some array with extra data
        raise NotImplementedError("Cannot init on both h5 and data")

    if h5:
        # Allow filename
        if isinstance(h5, (str, pathlib.Path)):
            fname = os.path.expandvars(h5)
            assert os.path.exists(fname), f"File does not exist: {fname}"

            with File(fname, "r") as hh5:
                pp = particle_paths(hh5)
                assert len(pp) == 1, f"Number of particle paths in {h5}: {len(pp)}"
                data = load_bunch_data(hh5[pp[0]])

        elif isinstance(h5, File):
            pp = particle_paths(h5)
            assert len(pp) == 1, f"Number of particle paths in {h5}: {len(pp)}"
            data = load_bunch_data(h5[pp[0]])
        else:
            # Try dict
            data = load_bunch_data(h5)
    else:
        # Fill out data. Exclude species.
        data = full_data(data)
        species = list(set(data["species"]))

        # Allow for empty data (len=0). Otherwise, check species.
        if len(species) >= 1:
            assert len(species) == 1, f"mixed species are not allowed: {species}"
            data["species"] = species[0]

    self._settable_array_keys = [
        "x",
        "px",
        "y",
        "py",
        "z",
        "pz",
        "t",
        "status",
        "weight",
    ]
    # Optional data
    for k in ["id"]:
        if k in data:
            self._settable_array_keys.append(k)

    self._settable_scalar_keys = ["species"]
    self._settable_keys = self._settable_array_keys + self._settable_scalar_keys
    # Internal data. Only allow settable keys

    def fix_dtype(key: str, arr: np.ndarray) -> np.ndarray:
        if key in self._settable_scalar_keys:
            return arr  # passthrough
        if key in {"id", "status"}:
            return _round_to_int_array(arr)
        return np.asarray(arr, dtype=float)

    self._data = {key: fix_dtype(key, data[key]) for key in self._settable_keys}

Jx property

Jx

Normalized amplitude J in the x-px plane

Jy property

Jy

Normalized amplitude J in the y-py plane

Lz property

Lz

Angular momentum around the z axis in m*eV/c Lz = x * py - y * px

average_current property

average_current

Simple average current = charge / dt in [A], with dt = (max_t - min_t) If particles are in \(t\) coordinates, will try dt = (max_z - min_z) / (c_light * beta_z)

beta property

beta

Relativistic beta

beta_x property writable

beta_x

Relativistic beta, x component

beta_y property writable

beta_y

Relativistic beta, y component

beta_z property writable

beta_z

Relativistic beta, z component

charge property writable

charge

Total charge in C

data property

data

Internal data dict

energy property

energy

Total energy in eV

gamma property writable

gamma

Relativistic gamma

higher_order_energy property

higher_order_energy

Fits a quadratic (order=2) to the Energy vs. time, and returns the energy with this subtracted.

higher_order_energy_spread property

higher_order_energy_spread

Legacy syntax to compute the standard deviation of higher_order_energy.

id property writable

id

id integer

in_t_coordinates property

in_t_coordinates

Returns True if all particles have the same t coordinate

in_z_coordinates property

in_z_coordinates

Returns True if all particles have the same z coordinate

kinetic_energy property

kinetic_energy

Kinetic energy in eV

mass property

mass

Rest mass in eV

n_alive property

n_alive

Number of alive particles, defined by status == 1

n_dead property

n_dead

Number of dead particles, defined by status != 1

n_particle property

n_particle

Total number of particles. Same as len

norm_emit_4d property

norm_emit_4d

Normalized emittance in the xy planes (4D)

norm_emit_x property

norm_emit_x

Normalized emittance in the x plane

norm_emit_y property

norm_emit_y

Normalized emittance in the y plane

p property

p

Total momemtum in eV/c

pr property

pr

Momentum in the radial direction in eV/c r_hat = cos(theta) xhat + sin(theta) yhat pr = p dot r_hat

ptheta property

ptheta

Momentum in the polar theta direction. theta_hat = -sin(theta) xhat + cos(theta) yhat ptheta = p dot theta_hat Note that Lz = r*ptheta

px property writable

px

px coordinate in [eV/c]

px_bar property

px_bar

Normalized px in units of sqrt(m)

py property writable

py

py coordinate in [eV/c]

py_bar property

py_bar

Normalized py in units of sqrt(m)

pz property writable

pz

pz coordinate in [eV/c]

r property

r

Radius in the xy plane: r = sqrt(x^2 + y^2) in m

species property writable

species

species string

species_charge property

species_charge

Species charge in C

status property writable

status

status coordinate in [1]

t property writable

t

t coordinate in [s]

theta property

theta

Angle in xy plane: theta = arctan2(y, x) in radians

weight property writable

weight

weight coordinate in [C]

x property writable

x

x coordinate in [m]

x_bar property

x_bar

Normalized x in units of sqrt(m)

xp property

xp

x slope px/pz (dimensionless)

y property writable

y

y coordinate in [m]

y_bar property

y_bar

Normalized y in units of sqrt(m)

yp property

yp

y slope py/pz (dimensionless)

z property writable

z

z coordinate in [m]

__add__

__add__(other)

Overloads the + operator to join particle groups. Simply calls join_particle_groups

Source code in beamphysics/particles.py
1923
1924
1925
1926
1927
1928
def __add__(self, other):
    """
    Overloads the + operator to join particle groups.
    Simply calls join_particle_groups
    """
    return join_particle_groups(self, other)

__contains__

__contains__(item)

Checks internal data

Source code in beamphysics/particles.py
1930
1931
1932
def __contains__(self, item):
    """Checks internal data"""
    return True if item in self._data else False

__eq__

__eq__(other)

Check equality of species and internal data

Source code in beamphysics/particles.py
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
def __eq__(self, other):
    """Check equality of species and internal data"""
    if isinstance(other, ParticleGroup):
        if self.species != other.species:
            return False
        keys = ["x", "px", "y", "py", "z", "pz", "t", "status", "weight"]
        for key in keys:
            if not np.allclose(self[key], other[key]):
                return False

        # Compare ids without triggering assign_id as a side effect. If only
        # one operand has explicit ids, compare them against the default ids
        # (1..n) that assign_id would otherwise produce for the other.
        self_has_id = "id" in self._data
        other_has_id = "id" in other._data
        if self_has_id or other_has_id:
            self_id = self._data["id"] if self_has_id else default_id(len(self))
            other_id = other._data["id"] if other_has_id else default_id(len(other))
            return np.array_equal(self_id, other_id)
        return True

    return NotImplemented

__getitem__

__getitem__(key: str)

Returns a property or statistical quantity that can be computed:

  • P['x'] returns the x array
  • P['sigmx_x'] returns the std(x) scalar
  • P['norm_emit_x'] returns the norm_emit_x scalar

Parts can also be given. Example: P[0:10] returns a new ParticleGroup with the first 10 elements.

Source code in beamphysics/particles.py
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
def __getitem__(self, key: str):
    """
    Returns a property or statistical quantity that can be computed:

    - `P['x']` returns the x array
    - `P['sigmx_x']` returns the std(x) scalar
    - `P['norm_emit_x']` returns the norm_emit_x scalar

    Parts can also be given. Example: `P[0:10]` returns a new ParticleGroup with the first 10 elements.
    """

    # Allow for non-string operations:
    if not isinstance(key, str):
        return particle_parts(self, key)

    # 'z/c' special case
    if key == "z/c":
        return self["z"] / (c_light)

    if key.startswith("cov_"):
        subkeys = key.removeprefix("cov_").split("__")
        assert (
            len(subkeys) == 2
        ), f"Too many properties in covariance request: {key}"
        return self.cov(*subkeys)[0, 1]
    if key.startswith("delta_"):
        return self.delta(key[6:])
    if key.startswith("sigma_"):
        return self.std(key[6:])
    if key.startswith("mean_"):
        return self.avg(key[5:])
    if key.startswith("min_"):
        return self.min(key[4:])
    if key.startswith("max_"):
        return self.max(key[4:])
    if key.startswith("ptp_"):
        return self.ptp(key[4:])
    if key.startswith("twiss_"):
        twiss_key = key[6:]
        plane = twiss_key[-1]
        if plane not in {"x", "y"}:
            raise KeyError(
                f"Invalid twiss key: {key} ({twiss_key}). Must end in plane 'x' or 'y'."
            )
        return self.twiss(plane)[twiss_key]

    if key == "bunching":
        raise ValueError(
            "'bunching' is not a valid key on its own. There should be a suffix to indicate "
            "wavelength, such as: 'bunching_1.23e-4', 'bunching_1.23e-4_nm', or 'bunching_phase_1.23e-4'"
        )

    if "bunching" in key:
        wavelength = parse_bunching_str(key)
        bunching = self.bunching(wavelength)  # complex

        # abs or arg (angle):
        if "phase_" in key:
            return np.angle(bunching)
        return np.abs(bunching)

    return getattr(self, key)

__len__

__len__()

Return the number of particles.

Source code in beamphysics/particles.py
1957
1958
1959
def __len__(self):
    """Return the number of particles."""
    return len(self[self._settable_array_keys[0]])

__repr__

__repr__()

Return a detailed string representation with memory location.

Source code in beamphysics/particles.py
1966
1967
1968
1969
def __repr__(self):
    """Return a detailed string representation with memory location."""
    memloc = hex(id(self))
    return f"<ParticleGroup with {self.n_particle} particles at {memloc}>"

__str__

__str__()

Return a human-readable summary string.

Source code in beamphysics/particles.py
1961
1962
1963
1964
def __str__(self):
    """Return a human-readable summary string."""
    s = f"ParticleGroup with {self.n_particle} particles with total charge {self.charge} C"
    return s

apply_wakefield

apply_wakefield(wakefield: WakefieldBase, length: float, inplace: bool = False, include_self_kick: bool = True)

Apply wakefield momentum kicks to this ParticleGroup.

Parameters:

  • wakefield (WakefieldBase) –

    A wakefield object providing the particle_kicks(z, weight) method.

  • length (float) –

    Length over which the wakefield acts [m].

  • inplace (bool, default: False ) –

    If True, modifies in place. If False, returns a modified copy. Default is False.

  • include_self_kick (bool, default: True ) –

    Whether to include the self-kick term. Default is True.

Returns:

  • ParticleGroup or None

    Modified ParticleGroup if inplace=False, otherwise None.

Examples:

::

from beamphysics.wakefields import ResistiveWallWakefield
wake = ResistiveWallWakefield.from_material("copper-slac-pub-10707", radius=2.5e-3)
P_after = P.apply_wakefield(wake, length=10.0)
Source code in beamphysics/particles.py
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
def apply_wakefield(
    self,
    wakefield: WakefieldBase,
    length: float,
    inplace: bool = False,
    include_self_kick: bool = True,
):
    """
    Apply wakefield momentum kicks to this ParticleGroup.

    Parameters
    ----------
    wakefield : WakefieldBase
        A wakefield object providing the `particle_kicks(z, weight)` method.
    length : float
        Length over which the wakefield acts [m].
    inplace : bool, optional
        If True, modifies in place. If False, returns a modified copy.
        Default is False.
    include_self_kick : bool, optional
        Whether to include the self-kick term. Default is True.

    Returns
    -------
    ParticleGroup or None
        Modified ParticleGroup if inplace=False, otherwise None.

    Examples
    --------
    ::

        from beamphysics.wakefields import ResistiveWallWakefield
        wake = ResistiveWallWakefield.from_material("copper-slac-pub-10707", radius=2.5e-3)
        P_after = P.apply_wakefield(wake, length=10.0)
    """
    if not inplace:
        P = self.copy()
    else:
        P = self

    # Extract z positions
    if P.in_t_coordinates:
        z = np.asarray(P.z)
    else:
        z = -c_light * np.asarray(P.t)

    weight = np.asarray(P.weight)
    kicks = wakefield.particle_kicks(z, weight, include_self_kick=include_self_kick)
    P.pz += kicks * length

    if not inplace:
        return P

assign_id

assign_id()

Assign unique integer IDs to all particles.

Sets self.id to integers from 1 to n_particle.

Source code in beamphysics/particles.py
393
394
395
396
397
398
399
400
401
def assign_id(self):
    """
    Assign unique integer IDs to all particles.

    Sets ``self.id`` to integers from 1 to ``n_particle``.
    """
    if "id" not in self._settable_array_keys:
        self._settable_array_keys.append("id")
    self.id = default_id(self["n_particle"])

avg

avg(key: str)

Return the weighted average of a particle property.

Parameters:

  • key (str) –

    Any valid particle property name.

Returns:

  • float

    Weighted average using self.weight as weights.

Source code in beamphysics/particles.py
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
def avg(self, key: str):
    """
    Return the weighted average of a particle property.

    Parameters
    ----------
    key : str
        Any valid particle property name.

    Returns
    -------
    float
        Weighted average using ``self.weight`` as weights.
    """
    dat = self[key]  # equivalent to self.key for accessing properties above
    if np.isscalar(dat):
        return dat
    return np.average(dat, weights=self.weight)

bunching

bunching(wavelength)

Calculate the normalized bunching parameter, which is the magnitude of the complex sum of weighted exponentials at a given point.

The formula for bunching is given by:

\[ B(z, \lambda) = \frac{\left|\sum w_i e^{i k z_i}\right|}{\sum w_i} \]

where: - \( z \) is the position array, - \( \lambda \) is the wavelength, - \( k = \frac{2\pi}{\lambda} \) is the wave number, - \( w_i \) are the weights.

Parameters:

  • wavelength (float) –

    Wavelength of the wave.

Returns:

  • complex

    The normalized bunching parameter.

Raises:

  • ValueError

    If wavelength is not a positive number.

Source code in beamphysics/particles.py
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
def bunching(self, wavelength):
    r"""
    Calculate the normalized bunching parameter, which is the magnitude of the
    complex sum of weighted exponentials at a given point.

    The formula for bunching is given by:

    $$
    B(z, \lambda) = \frac{\left|\sum w_i e^{i k z_i}\right|}{\sum w_i}
    $$

    where:
    - \( z \) is the position array,
    - \( \lambda \) is the wavelength,
    - \( k = \frac{2\pi}{\lambda} \) is the wave number,
    - \( w_i \) are the weights.

    Parameters
    ----------
    wavelength : float
        Wavelength of the wave.


    Returns
    -------
    complex
        The normalized bunching parameter.

    Raises
    ------
    ValueError
        If `wavelength` is not a positive number.
    """

    if self.in_z_coordinates:
        # Approximate z
        z = self.t * self.avg("beta_z") * c_light
    else:
        z = self.z

    return statistics.bunching(z, wavelength, weight=self.weight)

copy

copy()

Return a deep copy of this ParticleGroup.

Returns:

Source code in beamphysics/particles.py
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
def copy(self):
    """
    Return a deep copy of this ParticleGroup.

    Returns
    -------
    ParticleGroup
        An independent copy with no shared data.
    """
    return deepcopy(self)

cov

cov(*keys)

Covariance matrix from any properties

Example: P = ParticleGroup(h5) P.cov('x', 'px', 'y', 'py')

Source code in beamphysics/particles.py
739
740
741
742
743
744
745
746
747
748
749
def cov(self, *keys):
    """
    Covariance matrix from any properties

    Example:
    P = ParticleGroup(h5)
    P.cov('x', 'px', 'y', 'py')

    """
    dats = np.array([self[key] for key in keys])
    return np.cov(dats, aweights=self.weight)

delta

delta(key: str)

Return an attribute array relative to its weighted mean.

Parameters:

  • key (str) –

    Any valid particle property name.

Returns:

  • ndarray

    Array of self[key] - self.avg(key).

Source code in beamphysics/particles.py
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
def delta(self, key: str):
    """
    Return an attribute array relative to its weighted mean.

    Parameters
    ----------
    key : str
        Any valid particle property name.

    Returns
    -------
    numpy.ndarray
        Array of ``self[key] - self.avg(key)``.
    """
    return self[key] - self.avg(key)

drift

drift(delta_t)

Drift all particles for a given time interval.

Updates x, y, z, and t in-place using each particle's velocity.

Parameters:

  • delta_t (float or ndarray) –

    Time interval in seconds. Can be a scalar (same for all particles) or an array with one value per particle.

Source code in beamphysics/particles.py
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
def drift(self, delta_t):
    """
    Drift all particles for a given time interval.

    Updates x, y, z, and t in-place using each particle's velocity.

    Parameters
    ----------
    delta_t : float or numpy.ndarray
        Time interval in seconds. Can be a scalar (same for all particles)
        or an array with one value per particle.
    """
    self.x = self.x + self.beta_x * c_light * delta_t
    self.y = self.y + self.beta_y * c_light * delta_t
    self.z = self.z + self.beta_z * c_light * delta_t
    self.t = self.t + delta_t

drift_to_t

drift_to_t(t=None)

Drifts all particles to the same t.

If no t is given, particles will be drifted to the average t

Source code in beamphysics/particles.py
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
def drift_to_t(self, t=None):
    """
    Drifts all particles to the same t.

    If no t is given, particles will be drifted to the average t
    """
    if t is None:
        t = self.avg("t")
    dt = t - self.t
    self.drift(dt)
    # Fix t to be exactly this value
    self.t = np.full(self.n_particle, t)

drift_to_z

drift_to_z(z=None)

Drift all particles to the same z position.

Each particle is drifted by the time needed to reach z given its longitudinal velocity. After drifting, all z values are set exactly to z.

Parameters:

  • z (float, default: None ) –

    Target z position in meters. If None, particles are drifted to the mean z.

Source code in beamphysics/particles.py
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
def drift_to_z(self, z=None):
    """
    Drift all particles to the same z position.

    Each particle is drifted by the time needed to reach `z` given
    its longitudinal velocity. After drifting, all z values are set
    exactly to `z`.

    Parameters
    ----------
    z : float, optional
        Target z position in meters. If None, particles are drifted
        to the mean z.
    """
    if z is None:
        z = self.avg("z")
    dt = (z - self.z) / (self.beta_z * c_light)
    self.drift(dt)
    # Fix z to be exactly this value
    self.z = np.full(self.n_particle, z)

fractional_split

fractional_split(fractions: Union[float, int, list], key: str)

Split particles based on a given array key and a list of specified fractions or a single fraction.

Parameters:

  • fractions (float or list of float) –

    A fraction or a list of fractions used for splitting the particles. All values must be between 0 and 1 (exclusive).

  • key (str) –

    The attribute of particles to be used for sorting and splitting (e.g., 'z' for longitudinal position).

Returns:

  • particle_groups ( list of ParticleGroup ) –

    A list of ParticleGroup objects, each representing a subset of particles based on the specified fractions.

Description

This function splits the given group of particles into multiple subsets based on the provided attribute (e.g., position). The splits are determined such that each specified fraction of the total particle weights is separated. The function first sorts the particles by the specified key, computes the cumulative sum of weights, and determines the split values. It then returns a list of ParticleGroup objects representing the split subsets.

Source code in beamphysics/particles.py
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
def fractional_split(self, fractions: Union[float, int, list], key: str):
    """
    Split particles based on a given array key and a list of specified fractions or a single fraction.

    Parameters
    ----------
    fractions : float or list of float
        A fraction or a list of fractions used for splitting the particles. All values must be between 0 and 1 (exclusive).

    key : str
        The attribute of particles to be used for sorting and splitting (e.g., 'z' for longitudinal position).

    Returns
    -------
    particle_groups : list of ParticleGroup
        A list of ParticleGroup objects, each representing a subset of particles based on the specified fractions.

    Description
    -----------
    This function splits the given group of particles into multiple subsets based on the provided attribute (e.g., position).
    The splits are determined such that each specified fraction of the total particle weights is separated.
    The function first sorts the particles by the specified key, computes the cumulative sum of weights,
    and determines the split values. It then returns a list of ParticleGroup objects representing the split subsets.

    """

    # Ensure fractions is a list
    if isinstance(fractions, (float, int)):
        fractions = [fractions]

    # Validate fraction values
    if any(f <= 0 or f >= 1 for f in fractions):
        raise ValueError("All fraction values must be between 0 and 1 (exclusive)")

    # Sort particles by the specified key
    ixs = np.argsort(self[key])
    sorted_particles = self[ixs]

    # Sorted weights
    ws = sorted_particles.weight
    total_weight = np.sum(ws)
    cw = np.cumsum(ws) / total_weight

    # Use vectorized searchsorted to determine split indices
    fractions = np.array(fractions)
    split_indices = np.searchsorted(cw, fractions, side="right")

    # Create ParticleGroup subsets for each split
    particle_groups = []
    previous_index = 0
    for isplit in split_indices:
        particle_groups.append(sorted_particles[previous_index:isplit])
        previous_index = isplit

    # Add the remaining particles to the last group
    if previous_index < len(sorted_particles):
        particle_groups.append(sorted_particles[previous_index:])

    return particle_groups

from_bmad classmethod

from_bmad(bmad_dict)

Convert Bmad particle data to a ParticleGroup.

This reverses the conversion done by to_bmad, mapping Bmad phase space coordinates back to a ParticleGroup data format.

Parameters:

  • bmad_dict (dict) –

    A dictionary containing Bmad phase space coordinates with keys: 'x', 'px', 'y', 'py', 'z', 'pz', 'charge', 'species', 'tref', 'state'

Returns:

Source code in beamphysics/particles.py
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
@classmethod
def from_bmad(cls, bmad_dict):
    """
    Convert Bmad particle data to a ParticleGroup.

    This reverses the conversion done by to_bmad, mapping
    Bmad phase space coordinates back to a ParticleGroup data format.

    Parameters
    ----------
    bmad_dict : dict
        A dictionary containing Bmad phase space coordinates
        with keys:
        'x', 'px', 'y', 'py', 'z', 'pz',
        'charge', 'species', 'tref', 'state'

    Returns
    -------
    ParticleGroup
    """
    data = bmad.bmad_to_particlegroup_data(bmad_dict)
    return cls(data=data)

from_genesis4 classmethod

from_genesis4(h5: str | Path | File, smear: bool = False, wrap: bool = False, z0: float = 0.0, slices: Optional[list[int]] = None, equal_weights: bool = False, cutoff: float = 0.0, n_particle: Optional[int] = None, rng: Optional[int | Generator] = None) -> ParticleGroup

Create a ParticleGroup from a Genesis4 .par HDF5 file.

This method loads slice-based macroparticle data from a Genesis 4 .par file, reconstructs the 6D phase space distribution, and returns it as a ParticleGroup. Supports optional smearing and resampling.

Parameters:

  • h5 (str, pathlib.Path, or h5py.File) –

    Path to a Genesis 4 .par HDF5 file, or an open h5py file handle.

  • smear (bool, default: False ) –

    Genesis4 often samples the beam by skipping slices in a step called 'sample'. This will smear out the theta coordinate over these slices, preserving the modulus.

  • slices (list of int, default: None ) –

    Specific slice indices to include. If None, all available slices are used.

  • wrap (bool, default: False ) –

    If true, the z position within a slice will be modulo the slice length.

  • z0 (float, default: 0.0 ) –

    Initial z-position for slice index 1, in meters.

  • equal_weights (bool, default: False ) –

    If True, particles are resampled within each slice so that all output particles have equal charge weights. Only existing particles are used (the beam is never upsampled), so the output count is generally smaller. Useful for simulations that require uniform macroparticles. Mutually exclusive with n_particle.

  • cutoff (float, default: 0.0 ) –

    Minimum per-particle weight in Coulombs. By default (0.0) nothing is dropped for being sub-physical; pass a positive value (e.g. the elementary charge ~1.6e-19) to discard slices whose macroparticles would carry less than a single electron. Zero-current and non-finite slices are always removed. Ignored for one4one beams, where each macroparticle is a single electron of weight exactly the elementary charge.

  • n_particle (int, default: None ) –

    Subsample the beam to exactly this many particles as it is read, retaining the per-slice weights (rescaled once so the total charge is conserved exactly) and thinning each slice in proportion to its particle count so the charge profile is preserved. Only the selected particles are read from the file — huge (e.g. one4one) files are downsampled immediately, without ever loading the full beam. Values >= the number of particles in the file are a no-op. Use genesis4_parfile_n_particle to find the file's total count. Mutually exclusive with equal_weights.

  • rng (Generator or seed, default: None ) –

    Random number generator or seed used for smearing and resampling. If None, a new default RNG is created.

Returns:

  • ParticleGroup

    A ParticleGroup instance constructed from the .par file.

Source code in beamphysics/particles.py
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
@classmethod
def from_genesis4(
    cls,
    h5: str | pathlib.Path | File,
    smear: bool = False,
    wrap: bool = False,
    z0: float = 0.0,
    slices: Optional[list[int]] = None,
    equal_weights: bool = False,
    cutoff: float = 0.0,
    n_particle: Optional[int] = None,
    rng: Optional[int | np.random.Generator] = None,
) -> ParticleGroup:
    """
    Create a ParticleGroup from a Genesis4 `.par` HDF5 file.

    This method loads slice-based macroparticle data from a Genesis 4 `.par`
    file, reconstructs the 6D phase space distribution, and returns it as a
    ParticleGroup. Supports optional smearing and resampling.

    Parameters
    ----------
    h5 : str, pathlib.Path, or h5py.File
        Path to a Genesis 4 `.par` HDF5 file, or an open h5py file handle.

    smear : bool, default=False
        Genesis4 often samples the beam by skipping slices
        in a step called 'sample'.
        This will smear out the theta coordinate over these slices,
        preserving the modulus.

    slices : list of int, optional
        Specific slice indices to include. If None, all available slices are used.

    wrap: bool, default = False
        If true, the z position within a slice will be modulo the slice length.

    z0 : float, default=0.0
        Initial z-position for slice index 1, in meters.

    equal_weights : bool, default=False
        If True, particles are resampled within each slice so that all output
        particles have equal charge weights. Only existing particles are used
        (the beam is never upsampled), so the output count is generally
        smaller. Useful for simulations that require uniform macroparticles.
        Mutually exclusive with `n_particle`.

    cutoff : float, default=0.0
        Minimum per-particle weight in Coulombs. By default (0.0) nothing is
        dropped for being sub-physical; pass a positive value (e.g. the
        elementary charge ~1.6e-19) to discard slices whose macroparticles
        would carry less than a single electron. Zero-current and non-finite
        slices are always removed. Ignored for one4one beams, where each
        macroparticle is a single electron of weight exactly the elementary
        charge.

    n_particle : int, optional
        Subsample the beam to exactly this many particles as it is read,
        retaining the per-slice weights (rescaled once so the total charge
        is conserved exactly) and thinning each slice in proportion to its
        particle count so the charge profile is preserved. Only the
        selected particles are read from the file — huge (e.g. one4one)
        files are downsampled immediately, without ever loading the full
        beam. Values >= the number of particles in the file are a no-op.
        Use `genesis4_parfile_n_particle` to find the file's total count.
        Mutually exclusive with `equal_weights`.

    rng : numpy.random.Generator or seed, optional
        Random number generator or seed used for smearing and resampling.
        If None, a new default RNG is created.

    Returns
    -------
    ParticleGroup
        A ParticleGroup instance constructed from the `.par` file.
    """
    data = genesis4_par_to_data(
        h5=h5,
        smear=smear,
        wrap=wrap,
        z0=z0,
        slices=slices,
        equal_weights=equal_weights,
        cutoff=cutoff,
        n_particle=n_particle,
        rng=rng,
    )
    return cls(data=data)

higher_order_energy_calc

higher_order_energy_calc(order=2)

Fits a polynmial with order order to the Energy vs. time, , and returns the energy with this subtracted.

Source code in beamphysics/particles.py
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
def higher_order_energy_calc(self, order=2):
    """
    Fits a polynmial with order `order` to the Energy vs. time, , and returns the energy with this subtracted.
    """
    # order=2
    if self.std("z") < 1e-12:
        # must be at a screen. Use t
        t = self.t
    else:
        # All particles at the same time. Use z to calc t
        t = self.z / c_light
    energy = self.energy

    best_fit_coeffs = np.polynomial.polynomial.polyfit(t, energy, order)
    best_fit = np.polynomial.polynomial.polyval(t, best_fit_coeffs)
    return energy - best_fit

histogramdd

histogramdd(*keys, bins=10, range=None)

Wrapper for numpy.histogramdd, but accepts property names as keys.

Computes the multidimensional histogram of keys. Internally uses weights.

Example: P.histogramdd('x', 'y', bins=50) Returns: np.array with shape 50x50, edge list

Source code in beamphysics/particles.py
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
def histogramdd(self, *keys, bins=10, range=None):
    """
    Wrapper for numpy.histogramdd, but accepts property names as keys.

    Computes the multidimensional histogram of keys. Internally uses weights.

    Example:
        P.histogramdd('x', 'y', bins=50)
    Returns:
        np.array with shape 50x50, edge list

    """
    H, edges = np.histogramdd(
        np.array([self[k] for k in list(keys)]).T,
        weights=self.weight,
        bins=bins,
        range=range,
    )

    return H, edges

info

info(key: str)

Return information about a specific statistics key.

Parameters:

  • key (str) –

Returns:

  • dict

    Information on the specific statistics item, including the following keys: label, mathlabel, units, description, formula, reference, reference_url, category, dtype, and shape

Source code in beamphysics/particles.py
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
def info(self, key: str):
    """
    Return information about a specific statistics key.

    Parameters
    ----------
    key : str

    Returns
    -------
    dict
        Information on the specific statistics item, including the following keys:
        label, mathlabel, units, description, formula, reference,
        reference_url, category, dtype, and shape
    """
    from .standards.statistics import get_all_statistics_by_key

    return get_all_statistics_by_key()[key]

linear_point_transform

linear_point_transform(mat3: Union[ndarray, Sequence[Sequence[float]]]) -> None

Applies a linear transformation to the particle's spatial coordinates and their conjugate momenta.

The spatial coordinates (x, y, z) are transformed using: [x', y', z'] = mat3 @ [x, y, z]

The conjugate momenta (px, py, pz) are transformed using: [px', py', pz'] = inv(mat3.T) @ [px, py, pz]

Parameters:

  • mat3 (array-like of shape (3, 3)) –

    A 3x3 transformation matrix. Must be convertible to a NumPy array. Coordinates must be ordered as (x, y, z).

Raises:

  • ValueError

    If mat3 is not a 3x3 matrix.

  • LinAlgError

    If the transformation matrix is singular (i.e., not invertible).

Notes

This method modifies the object in-place.

Source code in beamphysics/particles.py
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
def linear_point_transform(
    self, mat3: Union[np.ndarray, Sequence[Sequence[float]]]
) -> None:
    """
    Applies a linear transformation to the particle's spatial coordinates and
    their conjugate momenta.

    The spatial coordinates (x, y, z) are transformed using:
        [x', y', z'] = mat3 @ [x, y, z]

    The conjugate momenta (px, py, pz) are transformed using:
        [px', py', pz'] = inv(mat3.T) @ [px, py, pz]

    Parameters
    ----------
    mat3 : array-like of shape (3, 3)
        A 3x3 transformation matrix. Must be convertible to a NumPy array.
        Coordinates must be ordered as (x, y, z).

    Raises
    ------
    ValueError
        If `mat3` is not a 3x3 matrix.
    LinAlgError
        If the transformation matrix is singular (i.e., not invertible).

    Notes
    -----
    This method modifies the object in-place.
    """
    mat3 = np.asarray(mat3, dtype=float)
    if mat3.shape != (3, 3):
        raise ValueError("mat3 must be a 3x3 matrix.")

    # Grab original coordinates
    x, y, z = self.x, self.y, self.z
    px, py, pz = self.px, self.py, self.pz

    # Transform spatial coordinates: [x', y', z'] = mat3 @ [x, y, z]
    self.x = mat3[0, 0] * x + mat3[0, 1] * y + mat3[0, 2] * z
    self.y = mat3[1, 0] * x + mat3[1, 1] * y + mat3[1, 2] * z
    self.z = mat3[2, 0] * x + mat3[2, 1] * y + mat3[2, 2] * z

    # Transform momenta using inverse-transpose: [px', py', pz'] = inv(mat3.T) @ [px, py, pz]
    # Calculate inverse-transpose matrix elements
    inv_mat3_T = np.linalg.inv(mat3.T)
    self.px = inv_mat3_T[0, 0] * px + inv_mat3_T[0, 1] * py + inv_mat3_T[0, 2] * pz
    self.py = inv_mat3_T[1, 0] * px + inv_mat3_T[1, 1] * py + inv_mat3_T[1, 2] * pz
    self.pz = inv_mat3_T[2, 0] * px + inv_mat3_T[2, 1] * py + inv_mat3_T[2, 2] * pz

max

max(key: str)

Return the maximum value of a particle property.

Parameters:

  • key (str) –

    Any valid particle property name.

Returns:

  • float

    Maximum value.

Source code in beamphysics/particles.py
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
def max(self, key: str):
    """
    Return the maximum value of a particle property.

    Parameters
    ----------
    key : str
        Any valid particle property name.

    Returns
    -------
    float
        Maximum value.
    """
    return np.max(self[key])

min

min(key: str)

Return the minimum value of a particle property.

Parameters:

  • key (str) –

    Any valid particle property name.

Returns:

  • float

    Minimum value.

Source code in beamphysics/particles.py
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
def min(self, key: str):
    """
    Return the minimum value of a particle property.

    Parameters
    ----------
    key : str
        Any valid particle property name.

    Returns
    -------
    float
        Minimum value.
    """
    return np.min(self[key])

plot

plot(key1='x', key2=None, bins=None, *, xlim=None, ylim=None, return_figure=False, tex=True, nice=True, ellipse=False, **kwargs)

1d or 2d density plot.

If one key is given, this will plot the density of that key. Example: .plot('x')

If two keys arg given, this will plot a 2d marginal plot. Example: .plot('x', 'px')

Parameters:

  • key1 (str, default: = 't' ) –

    Key to bin on the x-axis

  • key2 (str, default: = None ) –

    Key to bin on the y-axis.

  • bins (int, default: = None ) –

    Number of bins. If None, this will use a heuristic: bins = sqrt(n_particle/4)

  • xlim (tuple, default: = None ) –

    Manual setting of the x-axis limits. Note that these are in raw, unscaled units.

  • ylim (tuple, default: = None ) –

    Manual setting of the y-axis limits. Note that these are in raw, unscaled units.

  • tex (bool, default: = True ) –

    Use TEX for labels

  • nice (bool, default: = True ) –

    Scale to nice units

  • ellipse (bool, default: = True ) –

    If True, plot an ellipse representing the 2x2 sigma matrix

  • return_figure (bool, default: = False ) –

    If true, return a matplotlib.figure.Figure object

  • **kwargs

    Any additional kwargs to send to the the plot in: plt.subplots(**kwargs)

Returns:

  • None or fig: matplotlib.figure.Figure

    This only returns a figure object if return_figure=T, otherwise returns None

Source code in beamphysics/particles.py
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
def plot(
    self,
    key1="x",
    key2=None,
    bins=None,
    *,
    xlim=None,
    ylim=None,
    return_figure=False,
    tex=True,
    nice=True,
    ellipse=False,
    **kwargs,
):
    """
    1d or 2d density plot.

    If one key is given, this will plot the density of that key.
    Example:
        .plot('x')

    If two keys arg given, this will plot a 2d marginal plot.
    Example:
        .plot('x', 'px')


    Parameters
    ----------
    key1 : str, default = 't'
        Key to bin on the x-axis

    key2 : str, default = None
        Key to bin on the y-axis.

    bins : int, default = None
       Number of bins. If None, this will use a heuristic: bins = sqrt(n_particle/4)

    xlim : tuple, default = None
        Manual setting of the x-axis limits. Note that these are in raw, unscaled units.

    ylim : tuple, default = None
        Manual setting of the y-axis limits. Note that these are in raw, unscaled units.

    tex : bool, default = True
        Use TEX for labels

    nice : bool, default = True
        Scale to nice units

    ellipse : bool, default = True
        If True, plot an ellipse representing the
        2x2 sigma matrix

    return_figure : bool, default = False
        If true, return a matplotlib.figure.Figure object

    **kwargs
        Any additional kwargs to send to the the plot in: plt.subplots(**kwargs)


    Returns
    -------
    None or fig: matplotlib.figure.Figure
        This only returns a figure object if return_figure=T, otherwise returns None

    """

    if not key2:
        fig = density_plot(
            self, key=key1, bins=bins, xlim=xlim, tex=tex, nice=nice, **kwargs
        )
    else:
        fig = marginal_plot(
            self,
            key1=key1,
            key2=key2,
            bins=bins,
            xlim=xlim,
            ylim=ylim,
            tex=tex,
            nice=nice,
            ellipse=ellipse,
            **kwargs,
        )

    if return_figure:
        return fig

ptp

ptp(key: str)

Return the peak-to-peak (max - min) range of a particle property.

Parameters:

  • key (str) –

    Any valid particle property name.

Returns:

  • float

    Peak-to-peak value.

Source code in beamphysics/particles.py
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
def ptp(self, key: str):
    """
    Return the peak-to-peak (max - min) range of a particle property.

    Parameters
    ----------
    key : str
        Any valid particle property name.

    Returns
    -------
    float
        Peak-to-peak value.
    """
    return np.ptp(self[key])

resample

resample(n=0, equal_weights=False)

Resample particles randomly.

If n equals self.n_particle or n=0, particle indices will be scrambled.

Otherwise if weights are equal, a random subset of particles will be selected.

Otherwise if weights are not equal, particles will be sampled according to their weight using scipy.stats.rv_discrete. Note that this latter method can result in duplicate particles, and can be very slow for a large number of particles.

Parameters:

  • n (int, default: 0 ) –

    Number to resample. If n=0, this will use all particles.

  • equal_weights (bool, default: False ) –

    If True, will ensure that all particles have equal weights.

Returns:

Source code in beamphysics/particles.py
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
def resample(self, n=0, equal_weights=False):
    """
    Resample particles randomly.

    If n equals self.n_particle or n=0,
    particle indices will be scrambled.

    Otherwise if weights are equal, a random subset of particles will be selected.

    Otherwise if weights are not equal, particles will be sampled according
    to their weight using
    [scipy.stats.rv_discrete](https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.rv_discrete.html).
    Note that this latter method can result in duplicate particles,
    and can be very slow for a large number of particles.

    Parameters
    ----------
    n : int, default=0
        Number to resample.
        If n=0, this will use all particles.

    equal_weights : bool, default=False
        If True, will ensure that all particles have equal weights.

    Returns
    -------
    ParticleGroup
    """
    data = resample_particles(self, n, equal_weights=equal_weights)
    return ParticleGroup(data=data)

rotate

rotate(*, x_rot: float = 0.0, y_rot: float = 0.0, z_rot: float = 0.0, order: str = 'zxy', xc: float = 0.0, yc: float = 0.0, zc: float = 0.0) -> None

Rotate the beam inplace by the specified angles first around the z axis, then the x axis, finally around the y axis.

Parameters:

  • x_rot (float, default: 0.0 ) –

    Rotation around the x axis, radians

  • y_rot (float, default: 0.0 ) –

    Rotation around the y axis, radians

  • z_rot (float, default: 0.0 ) –

    Rotation around the z axis, radians

  • order (str, default: 'zxy' ) –

    A 3-character string specifying the rotation order (e.g., 'zxy', 'zyx'). Each character must be one of 'x', 'y', or 'z'.

  • xc (float, default: 0.0 ) –

    Center of rotation, x coordinate. Default to zero

  • yc (float, default: 0.0 ) –

    Center of rotation, y coordinate. Default to zero

  • zc (float, default: 0.0 ) –

    Center of rotation, z coordinate. Default to zero

Notes

This method modifies the object in-place.

Source code in beamphysics/particles.py
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
def rotate(
    self,
    *,
    x_rot: float = 0.0,
    y_rot: float = 0.0,
    z_rot: float = 0.0,
    order: str = "zxy",
    xc: float = 0.0,
    yc: float = 0.0,
    zc: float = 0.0,
) -> None:
    """
    Rotate the beam inplace by the specified angles first around the z axis, then the x axis, finally around the y axis.

    Parameters
    ----------
    x_rot : float, optional
        Rotation around the x axis, radians
    y_rot : float, optional
        Rotation around the y axis, radians
    z_rot : float, optional
        Rotation around the z axis, radians
    order : str
        A 3-character string specifying the rotation order (e.g., 'zxy', 'zyx').
        Each character must be one of 'x', 'y', or 'z'.
    xc : float
        Center of rotation, x coordinate. Default to zero
    yc : float
        Center of rotation, y coordinate. Default to zero
    zc : float
        Center of rotation, z coordinate. Default to zero

    Notes
    -----
    This method modifies the object in-place.
    """
    # Special case transformations
    if (x_rot == 0.0) and (y_rot == 0.0):
        return self.rotate_z(z_rot, xc=xc, yc=yc)
    if (x_rot == 0.0) and (z_rot == 0.0):
        return self.rotate_y(y_rot, xc=xc, zc=zc)
    if (z_rot == 0.0) and (y_rot == 0.0):
        return self.rotate_x(x_rot, yc=yc, zc=zc)

    # Shift beam
    self.x = self.x - xc
    self.y = self.y - yc
    self.z = self.z - zc

    # Perform rotation
    self.linear_point_transform(
        get_rotation_matrix(x_rot=x_rot, y_rot=y_rot, z_rot=z_rot, order=order)
    )

    # Shift back
    self.x = self.x + xc
    self.y = self.y + yc
    self.z = self.z + zc

rotate_x

rotate_x(theta: float, yc: float = 0.0, zc: float = 0.0) -> None

Rotate the object about the x-axis by angle theta (radians). Affects y–z coordinates and py–pz momenta.

Parameters:

  • theta (float) –

    Angle of rotation, radians

  • yc (float, default: 0.0 ) –

    Center of rotation, y coordinate. Default to zero

  • zc (float, default: 0.0 ) –

    Center of rotation, z coordinate. Default to zero

Source code in beamphysics/particles.py
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
def rotate_x(
    self,
    theta: float,
    yc: float = 0.0,
    zc: float = 0.0,
) -> None:
    """
    Rotate the object about the x-axis by angle `theta` (radians).
    Affects y–z coordinates and py–pz momenta.

    Parameters
    ----------
    theta : float
        Angle of rotation, radians
    yc : float
        Center of rotation, y coordinate. Default to zero
    zc : float
        Center of rotation, z coordinate. Default to zero
    """
    # Shift beam
    self.y = self.y - yc
    self.z = self.z - zc

    c = np.cos(theta)
    s = np.sin(theta)

    y, z = self.y, self.z
    py, pz = self.py, self.pz

    self.y = c * y - s * z
    self.z = s * y + c * z

    self.py = c * py - s * pz
    self.pz = s * py + c * pz

    # Shift back
    self.y = self.y + yc
    self.z = self.z + zc

rotate_y

rotate_y(theta: float, xc: float = 0.0, zc: float = 0.0) -> None

Rotate the object about the y-axis by angle theta (radians). Affects z–x coordinates and pz–px momenta.

Parameters:

  • theta (float) –

    Angle of rotation, radians

  • xc (float, default: 0.0 ) –

    Center of rotation, x coordinate. Default to zero

  • zc (float, default: 0.0 ) –

    Center of rotation, z coordinate. Default to zero

Source code in beamphysics/particles.py
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
def rotate_y(
    self,
    theta: float,
    xc: float = 0.0,
    zc: float = 0.0,
) -> None:
    """
    Rotate the object about the y-axis by angle `theta` (radians).
    Affects z–x coordinates and pz–px momenta.

    Parameters
    ----------
    theta : float
        Angle of rotation, radians
    xc : float
        Center of rotation, x coordinate. Default to zero
    zc : float
        Center of rotation, z coordinate. Default to zero
    """
    # Shift beam
    self.x = self.x - xc
    self.z = self.z - zc

    c = np.cos(theta)
    s = np.sin(theta)

    z, x = self.z, self.x
    pz, px = self.pz, self.px

    self.z = c * z - s * x
    self.x = s * z + c * x

    self.pz = c * pz - s * px
    self.px = s * pz + c * px

    # Shift beam
    self.x = self.x + xc
    self.z = self.z + zc

rotate_z

rotate_z(theta: float, xc: float = 0.0, yc: float = 0.0) -> None

Rotate the object about the z-axis by angle theta (radians). Affects x–y coordinates and px–py momenta.

Parameters:

  • theta (float) –

    Angle of rotation, radians

  • xc (float, default: 0.0 ) –

    Center of rotation, x coordinate. Default to zero

  • yc (float, default: 0.0 ) –

    Center of rotation, y coordinate. Default to zero

Source code in beamphysics/particles.py
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
def rotate_z(
    self,
    theta: float,
    xc: float = 0.0,
    yc: float = 0.0,
) -> None:
    """
    Rotate the object about the z-axis by angle `theta` (radians).
    Affects x–y coordinates and px–py momenta.

    Parameters
    ----------
    theta : float
        Angle of rotation, radians
    xc : float
        Center of rotation, x coordinate. Default to zero
    yc : float
        Center of rotation, y coordinate. Default to zero
    """
    # Shift beam
    self.x = self.x - xc
    self.y = self.y - yc

    c = np.cos(theta)
    s = np.sin(theta)

    x, y = self.x, self.y
    px, py = self.px, self.py

    self.x = c * x - s * y
    self.y = s * x + c * y

    self.px = c * px - s * py
    self.py = s * px + c * py

    # Shift beam
    self.x = self.x + xc
    self.y = self.y + yc

slice_plot

slice_plot(*keys, n_slice=100, slice_key=None, tex=True, nice=True, return_figure=False, xlim=None, ylim=None, **kwargs)

Plot slice statistics along the bunch.

Parameters:

  • *keys (str, default: () ) –

    Keys to plot slice statistics for (e.g., 'sigma_x', 'norm_emit_x').

  • n_slice (int, default: 100 ) –

    Number of slices. Default is 100.

  • slice_key (str, default: None ) –

    Key to slice on. If None, defaults to 'z' for t-coordinates or 't' for z-coordinates.

  • tex (bool, default: True ) –

    Use TeX for axis labels. Default is True.

  • nice (bool, default: True ) –

    Scale to nice units. Default is True.

  • return_figure (bool, default: False ) –

    If True, return the matplotlib Figure. Default is False.

  • xlim (tuple of float, default: None ) –

    Manual x-axis limits in raw units.

  • ylim (tuple of float, default: None ) –

    Manual y-axis limits in raw units.

  • **kwargs

    Additional keyword arguments passed to plt.subplots.

Returns:

  • None or Figure

    Returns a Figure only if return_figure=True.

Source code in beamphysics/particles.py
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
def slice_plot(
    self,
    *keys,
    n_slice=100,
    slice_key=None,
    tex=True,
    nice=True,
    return_figure=False,
    xlim=None,
    ylim=None,
    **kwargs,
):
    """
    Plot slice statistics along the bunch.

    Parameters
    ----------
    *keys : str
        Keys to plot slice statistics for (e.g., ``'sigma_x'``, ``'norm_emit_x'``).
    n_slice : int, optional
        Number of slices. Default is 100.
    slice_key : str, optional
        Key to slice on. If None, defaults to ``'z'`` for t-coordinates or
        ``'t'`` for z-coordinates.
    tex : bool, optional
        Use TeX for axis labels. Default is True.
    nice : bool, optional
        Scale to nice units. Default is True.
    return_figure : bool, optional
        If True, return the matplotlib Figure. Default is False.
    xlim : tuple of float, optional
        Manual x-axis limits in raw units.
    ylim : tuple of float, optional
        Manual y-axis limits in raw units.
    **kwargs
        Additional keyword arguments passed to ``plt.subplots``.

    Returns
    -------
    None or matplotlib.figure.Figure
        Returns a Figure only if ``return_figure=True``.
    """
    fig = slice_plot(
        self,
        *keys,
        n_slice=n_slice,
        slice_key=slice_key,
        tex=tex,
        nice=nice,
        xlim=xlim,
        ylim=ylim,
        **kwargs,
    )

    if return_figure:
        return fig

slice_statistics

slice_statistics(*keys, n_slice=100, slice_key=None)

Compute slice statistics along the bunch.

Parameters:

  • *keys (str, default: () ) –

    Statistical quantities to compute per slice (e.g., 'sigma_x', 'norm_emit_x', 'mean_energy').

  • n_slice (int, default: 100 ) –

    Number of slices. Default is 100.

  • slice_key (str, default: None ) –

    Key to slice on. If None, defaults to 'z' when particles are in t-coordinates, or 't' otherwise.

Returns:

  • dict

    Dictionary of slice statistics arrays, including a 'current' or 'density' key computed from charge per slice.

Source code in beamphysics/particles.py
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
def slice_statistics(self, *keys, n_slice=100, slice_key=None):
    """
    Compute slice statistics along the bunch.

    Parameters
    ----------
    *keys : str
        Statistical quantities to compute per slice (e.g., ``'sigma_x'``,
        ``'norm_emit_x'``, ``'mean_energy'``).
    n_slice : int, optional
        Number of slices. Default is 100.
    slice_key : str, optional
        Key to slice on. If None, defaults to ``'z'`` when particles are in
        t-coordinates, or ``'t'`` otherwise.

    Returns
    -------
    dict
        Dictionary of slice statistics arrays, including a
        ``'current'`` or ``'density'`` key computed from charge
        per slice.
    """

    if slice_key is None:
        if self.in_t_coordinates:
            slice_key = "z"

        else:
            slice_key = "t"

    if slice_key in ("t", "delta_t"):
        density_name = "current"
    else:
        density_name = "density"

    keys = set(keys)
    keys.add("mean_" + slice_key)
    keys.add("ptp_" + slice_key)
    keys.add("charge")
    slice_dat = slice_statistics(
        self, n_slice=n_slice, slice_key=slice_key, keys=keys
    )

    slice_dat[density_name] = slice_dat["charge"] / slice_dat["ptp_" + slice_key]

    return slice_dat

split

split(n_chunks=100, key='z')

Split the particle group into even chunks sorted by a key.

Parameters:

  • n_chunks (int, default: 100 ) –

    Number of chunks to split into. Default is 100.

  • key (str, default: 'z' ) –

    Particle attribute to sort by before splitting. Default is 'z'.

Returns:

  • list of ParticleGroup

    A list of ParticleGroup objects, one per chunk.

Source code in beamphysics/particles.py
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
def split(self, n_chunks=100, key="z"):
    """
    Split the particle group into even chunks sorted by a key.

    Parameters
    ----------
    n_chunks : int, optional
        Number of chunks to split into. Default is 100.
    key : str, optional
        Particle attribute to sort by before splitting. Default is ``'z'``.

    Returns
    -------
    list of ParticleGroup
        A list of ParticleGroup objects, one per chunk.
    """
    return split_particles(self, n_chunks=n_chunks, key=key)

std

std(key: str)

Return the weighted standard deviation of a particle property.

Parameters:

  • key (str) –

    Any valid particle property name.

Returns:

  • float

    Weighted standard deviation using self.weight as weights.

Source code in beamphysics/particles.py
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
def std(self, key: str):
    """
    Return the weighted standard deviation of a particle property.

    Parameters
    ----------
    key : str
        Any valid particle property name.

    Returns
    -------
    float
        Weighted standard deviation using ``self.weight`` as weights.
    """
    dat = self[key]
    if np.isscalar(dat):
        return 0
    avg_dat = self.avg(key)
    return np.sqrt(np.average((dat - avg_dat) ** 2, weights=self.weight))

to_bmad

to_bmad(p0c=None, tref=None)

Convert to Bmad phase space coordinates.

Bmad openPMD-beamphysics

Bmad x = x Bmad px = px/p0c Bmad y = y Bmad py = py/p0c Bmad z = -beta * c * (t - tref) Bmad pz = p/p0c - 1 Bmad t = t

Parameters:

  • p0c (float, default: None ) –

    Reference momentum times the speed of light (in eV). Default is None, which uses self['mean_p'].

  • tref (float, default: None ) –

    Reference time (in seconds). Default is None, which uses self['mean_t'].

Returns:

  • dict

    A dictionary containing Bmad phase space coordinates.

Source code in beamphysics/particles.py
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
def to_bmad(self, p0c=None, tref=None):
    """
    Convert to Bmad phase space coordinates.

    Bmad      openPMD-beamphysics
    ----      -------------------
    Bmad x  = x
    Bmad px = px/p0c
    Bmad y  = y
    Bmad py = py/p0c
    Bmad z = -beta * c * (t - tref)
    Bmad pz = p/p0c - 1
    Bmad t  = t

    Parameters
    ----------
    p0c : float, optional
        Reference momentum times the speed of light (in eV).
        Default is None, which uses self['mean_p'].
    tref : float, optional
        Reference time (in seconds).
        Default is None, which uses self['mean_t'].

    Returns
    -------
    dict
        A dictionary containing Bmad phase space coordinates.
    """
    return bmad.particlegroup_to_bmad(self, p0c=p0c, tref=tref)

twiss

twiss(plane='x', fraction=1, p0c=None)

Returns Twiss and Dispersion dict.

plane can be:

'x', 'y', or 'xy'

Optionally a fraction of the particles, based on amplitiude, can be specified.

Source code in beamphysics/particles.py
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
def twiss(self, plane="x", fraction=1, p0c=None):
    """
    Returns Twiss and Dispersion dict.

    plane can be:

    `'x'`, `'y'`, or `'xy'`

    Optionally a fraction of the particles, based on amplitiude, can be specified.
    """
    d = {}
    for p in plane:
        d.update(
            particle_twiss_dispersion(self, plane=p, fraction=fraction, p0c=p0c)
        )
    return d

twiss_match

twiss_match(beta=None, alpha=None, plane='x', p0c=None, inplace=False)

Returns a ParticleGroup with requested Twiss parameters.

See: statistics.matched_particles

Source code in beamphysics/particles.py
805
806
807
808
809
810
811
812
813
814
def twiss_match(self, beta=None, alpha=None, plane="x", p0c=None, inplace=False):
    """
    Returns a ParticleGroup with requested Twiss parameters.

    See: statistics.matched_particles
    """

    return matched_particles(
        self, beta=beta, alpha=alpha, plane=plane, p0c=p0c, inplace=inplace
    )

units

units(key: str) -> pmd_unit

Get the unit information (pmd_unit) for a given key.

Parameters:

  • key (str) –

    Any valid particle key name.

Returns:

  • pmd_unit
Source code in beamphysics/particles.py
418
419
420
421
422
423
424
425
426
427
428
429
430
431
def units(self, key: str) -> pmd_unit:
    """
    Get the unit information (`pmd_unit`) for a given key.

    Parameters
    ----------
    key : str
        Any valid particle key name.

    Returns
    -------
    pmd_unit
    """
    return pg_units(key)

wakefield_plot

wakefield_plot(wake: WakefieldBase, key=None, nice=True, ax=None, xlim=None, ylim=None, tex=True, bins=None, **kwargs)

Plot per-particle wakefield kicks overlaid with the bunch density.

This function overlays the computed wakefield kicks (in eV/m) as a scatter plot on the primary y-axis, and the corresponding particle density as a histogram on a secondary y-axis. The independent variable is chosen automatically based on the coordinate system or specified explicitly with key.

Parameters:

  • wake (WakefieldBase) –

    A wakefield object providing the particle_kicks(z, weight) method, returning longitudinal wakefield kicks in eV/m.

  • key (str, default: None ) –

    Key to use as the independent variable. If None, defaults to 'delta_z/c' or 'delta_t' depending on particle_group.in_t_coordinates.

  • nice (bool, default: True ) –

    If True, applies unit-aware scaling using SI prefixes (e.g., mm, ns).

  • ax (Axes, default: None ) –

    An existing Axes to plot into. If None, a new figure and axes are created.

  • xlim (tuple of float, default: None ) –

    Limits to apply to the x-axis, in native units.

  • ylim (tuple of float, default: None ) –

    Limits to apply to the y-axis (wakefield kick), in native units.

  • tex (bool, default: True ) –

    Whether to use TeX-style math formatting for labels.

  • bins (int or str, default: None ) –

    Number of bins to use for the density histogram.

  • kwargs (dict, default: {} ) –

    Additional keyword arguments passed to plt.subplots() if a new axis is created.

Returns:

  • fig ( Figure ) –

    The matplotlib figure containing the plot.

Source code in beamphysics/particles.py
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
def wakefield_plot(
    self,
    wake: WakefieldBase,
    key=None,
    nice=True,
    ax=None,
    xlim=None,
    ylim=None,
    tex=True,
    bins=None,
    **kwargs,
):
    """
    Plot per-particle wakefield kicks overlaid with the bunch density.

    This function overlays the computed wakefield kicks (in eV/m) as a scatter plot on
    the primary y-axis, and the corresponding particle density as a histogram on a
    secondary y-axis. The independent variable is chosen automatically based on the
    coordinate system or specified explicitly with `key`.

    Parameters
    ----------
    wake : WakefieldBase
        A wakefield object providing the `particle_kicks(z, weight)` method,
        returning longitudinal wakefield kicks in eV/m.

    key : str, optional
        Key to use as the independent variable. If None, defaults to 'delta_z/c' or 'delta_t'
        depending on `particle_group.in_t_coordinates`.

    nice : bool, default=True
        If True, applies unit-aware scaling using SI prefixes (e.g., mm, ns).

    ax : matplotlib.axes.Axes, optional
        An existing Axes to plot into. If None, a new figure and axes are created.

    xlim : tuple of float, optional
        Limits to apply to the x-axis, in native units.

    ylim : tuple of float, optional
        Limits to apply to the y-axis (wakefield kick), in native units.

    tex : bool, default=True
        Whether to use TeX-style math formatting for labels.

    bins : int or str, optional
        Number of bins to use for the density histogram.

    kwargs : dict
        Additional keyword arguments passed to `plt.subplots()` if a new axis is created.

    Returns
    -------
    fig : matplotlib.figure.Figure
        The matplotlib figure containing the plot.
    """

    wakefield_plot(
        self,
        wake,
        key=key,
        nice=nice,
        ax=ax,
        xlim=xlim,
        ylim=ylim,
        tex=tex,
        bins=bins,
        **kwargs,
    )

where

where(x)

Filter particles by a boolean condition.

Parameters:

  • x (array_like of bool) –

    Boolean mask or condition array. Particles where x is True are selected.

Returns:

  • ParticleGroup

    A new ParticleGroup containing only the selected particles.

Source code in beamphysics/particles.py
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
def where(self, x):
    """
    Filter particles by a boolean condition.

    Parameters
    ----------
    x : array_like of bool
        Boolean mask or condition array. Particles where `x` is True
        are selected.

    Returns
    -------
    ParticleGroup
        A new ParticleGroup containing only the selected particles.
    """
    return self[np.where(x)]

write

write(h5, name=None) -> None

Write particle data to an HDF5 file or group in openPMD format.

Parameters:

  • h5 (str or Path or File or Group) –

    Target for writing: - str or pathlib.Path: path to a new HDF5 file to be created (opened in mode "w"). Environment variables in path strings are expanded (via os.path.expandvars) before file creation. - h5py.File: an already opened file handle; a new "particles" group is created. - h5py.Group: an existing group; assumed to be an appropriate location to write data. This is for advanced users who know what they are doing. Any other object is treated as a group-like handle compatible with write_pmd_bunch.

  • name (str, default: None ) –

    Name for the subgroup/bunch written inside the "particles" group (or provided group). If None, write_pmd_bunch will write directly to the "particles" group.

Returns:

  • None

Examples:

Write to a new file:

>>> P.write("beam.h5")

Write to an already opened file:

>>> import h5py
>>> with h5py.File("beam.h5", "w") as f:
...     P.write(f)

Write into an existing group and name the bunch:

>>> import h5py
>>> with h5py.File("beam.h5", "w") as f:
...     grp = f["particles"]
...     particles.write(grp, name="bunch1")
Source code in beamphysics/particles.py
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
def write(self, h5, name=None) -> None:
    """
    Write particle data to an HDF5 file or group in openPMD format.

    Parameters
    ----------
    h5 : str or pathlib.Path or h5py.File or h5py.Group
        Target for writing:
        - str or pathlib.Path: path to a new HDF5 file to be created (opened in mode "w").
          Environment variables in path strings are expanded (via
          os.path.expandvars) before file creation.
        - h5py.File: an already opened file handle; a new "particles" group is created.
        - h5py.Group: an existing group; assumed to be an appropriate location to write data.
            This is for advanced users who know what they are doing.
            Any other object is treated as a group-like handle compatible with `write_pmd_bunch`.
    name : str, optional
        Name for the subgroup/bunch written inside the "particles" group (or provided group).
        If None, `write_pmd_bunch` will write directly to the "particles" group.

    Returns
    -------
    None

    Examples
    --------
    Write to a new file:
    >>> P.write("beam.h5")

    Write to an already opened file:
    >>> import h5py
    >>> with h5py.File("beam.h5", "w") as f:
    ...     P.write(f)

    Write into an existing group and name the bunch:
    >>> import h5py
    >>> with h5py.File("beam.h5", "w") as f:
    ...     grp = f["particles"]
    ...     particles.write(grp, name="bunch1")
    """
    if isinstance(h5, (str, pathlib.Path)):
        fname = os.path.expandvars(h5)
        g = File(fname, "w")
        pmd_init(g, basePath="/", particlesPath="particles")
        g = g.create_group("particles")
    elif isinstance(h5, File):
        pmd_init(h5, basePath="/", particlesPath="particles")
        g = h5.create_group("particles")
    else:
        g = h5

    write_pmd_bunch(g, self, name=name)

write_astra

write_astra(filePath, verbose=False, probe=False)

Write Astra style particles.

For now, the species must be electrons.

If probe, the six standard probe particles will be written.

Source code in beamphysics/particles.py
1199
1200
1201
1202
1203
1204
1205
1206
1207
def write_astra(self, filePath, verbose=False, probe=False):
    """
    Write Astra style particles.

    For now, the species must be electrons.

    If probe, the six standard probe particles will be written.
    """
    write_astra(self, filePath, verbose=verbose, probe=probe)

write_bmad

write_bmad(filePath, p0c=None, t_ref=0, verbose=False)

Write particle data in Bmad format.

Parameters:

  • filePath (str or Path) –

    Output file path.

  • p0c (float, default: None ) –

    Reference momentum in eV/c. If None, the average pz is used.

  • t_ref (float, default: 0 ) –

    Reference time in seconds. Default is 0.

  • verbose (bool, default: False ) –

    If True, print information during writing. Default is False.

Source code in beamphysics/particles.py
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
def write_bmad(self, filePath, p0c=None, t_ref=0, verbose=False):
    """
    Write particle data in Bmad format.

    Parameters
    ----------
    filePath : str or pathlib.Path
        Output file path.
    p0c : float, optional
        Reference momentum in eV/c. If None, the average pz is used.
    t_ref : float, optional
        Reference time in seconds. Default is 0.
    verbose : bool, optional
        If True, print information during writing. Default is False.
    """
    bmad.write_bmad(self, filePath, p0c=p0c, t_ref=t_ref, verbose=verbose)

write_elegant

write_elegant(filePath, verbose=False)

Write particle data in Elegant SDDS format.

Parameters:

  • filePath (str or Path) –

    Output file path.

  • verbose (bool, default: False ) –

    If True, print information during writing. Default is False.

Source code in beamphysics/particles.py
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
def write_elegant(self, filePath, verbose=False):
    """
    Write particle data in Elegant SDDS format.

    Parameters
    ----------
    filePath : str or pathlib.Path
        Output file path.
    verbose : bool, optional
        If True, print information during writing. Default is False.
    """
    write_elegant(self, filePath, verbose=verbose)

write_genesis2_beam_file

write_genesis2_beam_file(filePath, n_slice=None, verbose=False)

Write particle data as a Genesis 1.3 v2 beam file.

Parameters:

  • filePath (str or Path) –

    Output file path.

  • n_slice (int, default: None ) –

    Number of slices. If None, a default is determined automatically.

  • verbose (bool, default: False ) –

    If True, print information during writing. Default is False.

Source code in beamphysics/particles.py
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
def write_genesis2_beam_file(self, filePath, n_slice=None, verbose=False):
    """
    Write particle data as a Genesis 1.3 v2 beam file.

    Parameters
    ----------
    filePath : str or pathlib.Path
        Output file path.
    n_slice : int, optional
        Number of slices. If None, a default is determined automatically.
    verbose : bool, optional
        If True, print information during writing. Default is False.
    """
    # Get beam columns
    beam_columns = genesis2_beam_data(self, n_slice=n_slice)
    # Actually write the file
    write_genesis2_beam_file(filePath, beam_columns, verbose=verbose)

write_genesis4_beam

write_genesis4_beam(filePath, n_slice=None, return_input_str=False, verbose=False)

Write particle data as a Genesis 1.3 v4 beam file.

Parameters:

  • filePath (str or Path) –

    Output file path.

  • n_slice (int, default: None ) –

    Number of slices. If None, a default is determined automatically.

  • return_input_str (bool, default: False ) –

    Defaults to False.

  • verbose (bool, default: False ) –

    If True, print information during writing. Default is False.

Source code in beamphysics/particles.py
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
def write_genesis4_beam(
    self, filePath, n_slice=None, return_input_str=False, verbose=False
):
    """
    Write particle data as a Genesis 1.3 v4 beam file.

    Parameters
    ----------
    filePath : str or pathlib.Path
        Output file path.
    n_slice : int, optional
        Number of slices. If None, a default is determined automatically.
    return_input_str : bool, optional
        Defaults to False.
    verbose : bool, optional
        If True, print information during writing. Default is False.
    """
    return write_genesis4_beam(
        self,
        filePath,
        n_slice=n_slice,
        return_input_str=return_input_str,
        verbose=verbose,
    )

write_genesis4_distribution

write_genesis4_distribution(filePath, verbose=False)

Write particle data as a Genesis 1.3 v4 distribution HDF5 file.

Parameters:

  • filePath (str or Path) –

    Output file path.

  • verbose (bool, default: False ) –

    If True, print information during writing. Default is False.

Source code in beamphysics/particles.py
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
def write_genesis4_distribution(self, filePath, verbose=False):
    """
    Write particle data as a Genesis 1.3 v4 distribution HDF5 file.

    Parameters
    ----------
    filePath : str or pathlib.Path
        Output file path.
    verbose : bool, optional
        If True, print information during writing. Default is False.
    """
    write_genesis4_distribution(self, filePath, verbose=verbose)

write_gpt

write_gpt(filePath, asci2gdf_bin=None, verbose=False)

Write particle data in GPT (General Particle Tracer) format.

Parameters:

  • filePath (str or Path) –

    Output file path.

  • asci2gdf_bin (str, default: None ) –

    Path to the asci2gdf binary for GDF conversion. If None, an ASCII file is written.

  • verbose (bool, default: False ) –

    If True, print information during writing. Default is False.

Source code in beamphysics/particles.py
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
def write_gpt(self, filePath, asci2gdf_bin=None, verbose=False):
    """
    Write particle data in GPT (General Particle Tracer) format.

    Parameters
    ----------
    filePath : str or pathlib.Path
        Output file path.
    asci2gdf_bin : str, optional
        Path to the ``asci2gdf`` binary for GDF conversion. If None,
        an ASCII file is written.
    verbose : bool, optional
        If True, print information during writing. Default is False.
    """
    write_gpt(self, filePath, asci2gdf_bin=asci2gdf_bin, verbose=verbose)

write_impact

write_impact(filePath, cathode_kinetic_energy_ref=None, include_header=True, verbose=False)

Write particle data in Impact-T format.

Parameters:

  • filePath (str or Path) –

    Output file path.

  • cathode_kinetic_energy_ref (float, default: None ) –

    Reference kinetic energy at the cathode in eV. If None, the average kinetic energy is used.

  • include_header (bool, default: True ) –

    If True, include the Impact-T header line. Default is True.

  • verbose (bool, default: False ) –

    If True, print information during writing. Default is False.

Source code in beamphysics/particles.py
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
def write_impact(
    self,
    filePath,
    cathode_kinetic_energy_ref=None,
    include_header=True,
    verbose=False,
):
    """
    Write particle data in Impact-T format.

    Parameters
    ----------
    filePath : str or pathlib.Path
        Output file path.
    cathode_kinetic_energy_ref : float, optional
        Reference kinetic energy at the cathode in eV. If None, the
        average kinetic energy is used.
    include_header : bool, optional
        If True, include the Impact-T header line. Default is True.
    verbose : bool, optional
        If True, print information during writing. Default is False.
    """
    from .interfaces.impact import write_impact

    return write_impact(
        self,
        filePath,
        cathode_kinetic_energy_ref=cathode_kinetic_energy_ref,
        include_header=include_header,
        verbose=verbose,
    )

write_litrack

write_litrack(filePath, p0c=None, verbose=False)

Write particle data in LiTrack format.

Parameters:

  • filePath (str or Path) –

    Output file path.

  • p0c (float, default: None ) –

    Reference momentum in eV/c. If None, the average pz is used.

  • verbose (bool, default: False ) –

    If True, print information during writing. Default is False.

Source code in beamphysics/particles.py
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
def write_litrack(self, filePath, p0c=None, verbose=False):
    """
    Write particle data in LiTrack format.

    Parameters
    ----------
    filePath : str or pathlib.Path
        Output file path.
    p0c : float, optional
        Reference momentum in eV/c. If None, the average pz is used.
    verbose : bool, optional
        If True, print information during writing. Default is False.
    """
    return write_litrack(self, outfile=filePath, p0c=p0c, verbose=verbose)

write_lucretia

write_lucretia(filePath, ele_name='BEGINNING', t_ref=0, stop_ix=None, verbose=False)

Write particle data in Lucretia format.

Parameters:

  • filePath (str or Path) –

    Output file path.

  • ele_name (str, default: 'BEGINNING' ) –

    Element name for the Lucretia beam. Default is 'BEGINNING'.

  • t_ref (float, default: 0 ) –

    Reference time in seconds. Default is 0.

  • stop_ix (int, default: None ) –

    Stop index for the Lucretia beam structure.

  • verbose (bool, default: False ) –

    If True, print information during writing. Default is False.

Source code in beamphysics/particles.py
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
def write_lucretia(
    self, filePath, ele_name="BEGINNING", t_ref=0, stop_ix=None, verbose=False
):
    """
    Write particle data in Lucretia format.

    Parameters
    ----------
    filePath : str or pathlib.Path
        Output file path.
    ele_name : str, optional
        Element name for the Lucretia beam. Default is ``'BEGINNING'``.
    t_ref : float, optional
        Reference time in seconds. Default is 0.
    stop_ix : int, optional
        Stop index for the Lucretia beam structure.
    verbose : bool, optional
        If True, print information during writing. Default is False.
    """
    return write_lucretia(
        self, filePath, ele_name=ele_name, t_ref=t_ref, stop_ix=stop_ix
    )

write_opal

write_opal(filePath, verbose=False, dist_type='emitted')

Write particle data in OPAL (Object Oriented Parallel Accelerator Library) format.

Parameters:

  • filePath (str or Path) –

    Output file path.

  • verbose (bool, default: False ) –

    If True, print information during writing. Default is False.

  • dist_type (str, default: 'emitted' ) –

    Distribution type, either 'emitted' or 'injected'. Default is 'emitted'.

Source code in beamphysics/particles.py
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
def write_opal(self, filePath, verbose=False, dist_type="emitted"):
    """
    Write particle data in OPAL (Object Oriented Parallel Accelerator Library) format.

    Parameters
    ----------
    filePath : str or pathlib.Path
        Output file path.
    verbose : bool, optional
        If True, print information during writing. Default is False.
    dist_type : str, optional
        Distribution type, either ``'emitted'`` or ``'injected'``.
        Default is ``'emitted'``.
    """
    return write_opal(self, filePath, verbose=verbose, dist_type=dist_type)

write_simion

write_simion(filePath, color=0, flip_z_to_x=True, verbose=False)

Write particle data in SIMION format.

Parameters:

  • filePath (str or Path) –

    Output file path.

  • color (int, default: 0 ) –

    SIMION color index for particles. Default is 0.

  • flip_z_to_x (bool, default: True ) –

    If True, swap z and x axes for SIMION's coordinate convention. Default is True.

  • verbose (bool, default: False ) –

    If True, print information during writing. Default is False.

Source code in beamphysics/particles.py
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
def write_simion(self, filePath, color=0, flip_z_to_x=True, verbose=False):
    """
    Write particle data in SIMION format.

    Parameters
    ----------
    filePath : str or pathlib.Path
        Output file path.
    color : int, optional
        SIMION color index for particles. Default is 0.
    flip_z_to_x : bool, optional
        If True, swap z and x axes for SIMION's coordinate convention.
        Default is True.
    verbose : bool, optional
        If True, print information during writing. Default is False.
    """
    return write_simion(
        self, filePath, verbose=verbose, color=color, flip_z_to_x=flip_z_to_x
    )