Write examples¶
# Useful for debugging
%load_ext autoreload
%autoreload 2
from beamphysics import ParticleGroup, pmd_init
from h5py import File
import os
import tempfile
import numpy as np
import matplotlib.pyplot as plt
# Pick one:
# H5File = 'data/bmad_particles2.h5'
H5FILE = "data/distgen_particles.h5"
# H5FILE = 'data/astra_particles.h5'
P = ParticleGroup(H5FILE)
openPMD¶
The regular write routine writes in a proper openPMD format
P.write("openpmd_particles.h5")
An open h5 hande can also be used, but it needs to be properly initialized
with File("openpmd_particles.h5", "w") as h5:
pmd_init(h5, basePath="/", particlesPath="/")
P.write(h5)
This can be read in by another ParticleGroup
P2 = ParticleGroup("openpmd_particles.h5")
Check that they are the same:
P2 == P
True
Some codes read in the particle's absolute time from the particle group. This can cause numerical issues when the time offset is much larger than the relative offset of each particle in the bunch. OpenPMD-beamphysics allows writing the additional attribute timeOffset to the HDF5 file to avoid this. We demonstrate its use with the (extreme) example of a femtosecond long beam offset by one second in a machine, showing the difference between adding the time offset to the ParticleGroup attribute versus writing in the file.
# Control the reference time
ref_time = 1
bunch_len = 1e-15
# Generate a short bunch to demonstrate writing
P_short = P.copy()
P_short.t = np.random.normal(scale=bunch_len, size=len(P))
# Create a beam with naive offset
P_offset = P_short.copy()
P_offset.t = P_offset.t + ref_time
def write_read(pg, t_offset=0):
with tempfile.NamedTemporaryFile() as f:
pg.write(f.name, t_offset=t_offset)
return ParticleGroup.from_hdf5(f.name, include_time_offset=False)
# Write the beams, read back without offsets (ie what a simulation code separating timeOffset will see)
P_offset_loaded = write_read(P_offset)
P_short_loaded = write_read(P_short, t_offset=ref_time)
# Plot the longitudinal phase spaces against each other
# Note the numerical roundoff error in the t coordinate
plt.scatter(1e15 * P_short_loaded["delta_t"], P_short["pz"], s=1, label="With t_offset")
plt.scatter(
1e15 * P_offset_loaded["delta_t"], P_offset_loaded["pz"], s=1, label="Naive Write"
)
plt.xlabel(r"$\Delta t$ (fs)")
plt.ylabel(r"$p_z$ (eV/c)")
plt.legend()
<matplotlib.legend.Legend at 0x34ef9098710>
Astra¶
P.write_astra("astra_particles.txt")
!head astra_particles.txt
-1.289814080985e-05 1.712192978919e-05 0.000000000000e+00 -9.245284702413e-01 -3.316650265292e+00 2.210558337183e+02 1.819664001274e-05 0.000000000000e+00 1 5 -1.184861337727e-03 -2.101371437059e-03 0.000000000000e+00 -3.047044656318e+02 -3.039342419008e+02 -1.005645891013e+02 -9.745607002167e-04 1.000000000000e-06 1 5 -5.181307340245e-04 -2.178353405029e-03 0.000000000000e+00 5.525456229648e+02 2.416723877028e+02 -6.554342847563e+01 1.280843753434e-03 1.000000000000e-06 1 5 -1.773501610902e-03 2.864979597813e-03 0.000000000000e+00 -2.226004747820e+02 9.450238076106e+00 -1.055085411491e+02 3.835366744569e-04 1.000000000000e-06 1 5 1.686555815999e-03 -2.401048305081e-04 0.000000000000e+00 -1.891692499417e+02 4.859547751754e+01 3.339263495319e+02 1.902998338336e-03 1.000000000000e-06 1 5 -7.779454935491e-04 -6.800063114796e-04 0.000000000000e+00 6.716138938638e+01 -2.064173000222e+02 -1.405963302134e+02 1.779005092730e-04 1.000000000000e-06 1 5 -2.593702199590e-03 -2.301030494125e-03 0.000000000000e+00 -1.455653402031e+01 2.074634953296e+02 -1.397453142110e+02 1.368567098305e-03 1.000000000000e-06 1 5 1.997801509161e-03 2.648416193086e-03 0.000000000000e+00 -2.124047726665e+00 -6.792723569247e+01 6.931081537770e+01 2.616497721112e-04 1.000000000000e-06 1 5 1.999741847023e-03 -6.945690451493e-04 0.000000000000e+00 -9.991142908925e+01 -9.189412445573e+01 2.259539675809e+02 -8.681109991004e-04 1.000000000000e-06 1 5 -7.033822974359e-04 -5.677746866954e-04 0.000000000000e+00 7.520962264129e+02 3.125940718167e+02 -1.451032665210e+02 4.315191990002e-04 1.000000000000e-06 1 5
Check the readback:
from beamphysics.interfaces.astra import parse_astra_phase_file
import numpy as np
P1 = ParticleGroup(data=parse_astra_phase_file("astra_particles.txt"))
for k in ["x", "px", "y", "py", "z", "pz"]:
assert np.allclose(P[k], P1[k])
Bmad ASCII¶
P.write_bmad("bmad_particles.txt")
!head bmad_particles.txt
!ASCII::3 0 ! ix_ele, not used 1 ! n_bunch 10000 ! n_particle BEGIN_BUNCH electron 1.0000000000000003e-11 ! bunch_charge 0 ! z_center 0 ! t_center -1.184861337727e-03 -3.047044656318e+02 -2.101371437059e-03 -3.039342419008e+02 -9.563640602039e-13 1.204912446170e+02 1.000000000000e-15 1
Bmad dict¶
P.to_bmad()
{'x': array([-0.00118486, -0.00051813, -0.0017735 , ..., -0.00052658,
0.00252813, 0.00113815], shape=(10000,)),
'y': array([-0.00210137, -0.00217835, 0.00286498, ..., -0.00161154,
0.00160049, -0.0010351 ], shape=(10000,)),
'px': array([-0.69011879, 1.25144906, -0.50416317, ..., 0.60249121,
0.505233 , 0.74928061], shape=(10000,)),
'py': array([-0.68837433, 0.54735875, 0.02140365, ..., -0.07882388,
0.29433475, -0.25918357], shape=(10000,)),
'z': array([ 2.55529374e-07, -4.68009161e-07, -5.64739755e-08, ...,
-9.69805225e-09, 4.20165276e-07, 2.70278112e-07], shape=(10000,)),
'pz': array([ 0.01222356, 0.41059669, -0.4315584 , ..., -0.3093279 ,
0.02463904, 0.08781142], shape=(10000,)),
'charge': array([1.e-15, 1.e-15, 1.e-15, ..., 1.e-15, 1.e-15, 1.e-15],
shape=(10000,)),
'species': 'electron',
'p0c': np.float64(441.5246616725094),
'tref': np.float64(1.8196640012738958e-14),
'state': array([1, 1, 1, ..., 1, 1, 1], shape=(10000,))}
Check that the conversion preserves information. Note that == uses np.allclose, because there is roundoff error in the conversion.
assert P == P.from_bmad(P.to_bmad())
elegant¶
P.write_elegant("elegant_particles.txt", verbose=True)
writing 10000 particles to elegant_particles.txt
!head -n 20 elegant_particles.txt
SDDS1 ! ! Created using the openPMD-beamphysics Python package ! https://github.com/ChristopherMayes/openPMD-beamphysics ! species: electron ! ¶meter name=Charge, type=double, units=C, description="total charge in Coulombs" &end &column name=t, type=double, units=s, description="time in seconds" &end &column name=x, type=double, units=m, description="x in meters" &end &column name=xp, type=double, description="px/pz" &end &column name=y, type=double, units=m, description="y in meters" &end &column name=yp, type=double, description="py/pz" &end &column name=p, type=double, units="m$be$nc", description="relativistic gamma*beta" &end &data mode=ascii &end 1.0000000000000003e-11 10000 -9.563640602039e-13 -1.184861337727e-03 -2.528851507845e+00 -2.101371437059e-03 -2.522459145201e+00 8.746038804465e-04 1.299040393447e-12 -5.181307340245e-04 3.553064606663e+00 -2.178353405029e-03 1.554039289185e+00 1.218815081472e-03 4.017333144697e-13 -1.773501610902e-03 -1.926488019169e+00 2.864979597813e-03 8.178675472159e-02 4.911575363923e-04 1.921194978349e-12 1.686555815999e-03 -3.408564376497e-01 -2.401048305081e-04 8.756222989528e-02 1.151365619480e-03
Genesis 1.3 v2¶
P.write_genesis2_beam_file("genesis2.beam", n_slice=50, verbose=True)
Beam written: genesis2.beam
!head genesis2.beam
? VERSION=1.0 ? SIZE=50 ? COLUMNS TPOS CURPEAK GAMMA0 DELGAM EMITX EMITY RXBEAM RYBEAM XBEAM YBEAM PXBEAM PYBEAM ALPHAX ALPHAY -1.96236040e-12 2.75359318e+00 1.00000042e+00 3.87022120e-07 1.19639887e-06 9.26536585e-07 2.04948080e-03 1.83621527e-03 -2.06231144e-04 5.85738124e-05 1.38209180e-05 4.17553933e-05 -1.01973407e-02 7.54079832e-04 -1.88646733e-12 2.46723994e+00 1.00000043e+00 3.39783123e-07 1.04698472e-06 1.03470799e-06 2.05064183e-03 1.92139881e-03 -2.53107201e-05 -1.13678787e-04 -2.85190907e-05 -2.90105674e-05 -2.32400675e-02 -2.55583997e-03 -1.80734757e-12 2.61898031e+00 1.00000043e+00 3.65257966e-07 9.78086021e-07 1.03038032e-06 1.97065976e-03 1.88550433e-03 -7.56576638e-05 1.77044304e-04 -4.48907172e-05 6.98821768e-05 5.38708456e-03 4.70844009e-03 -1.72583745e-12 2.30361285e+00 1.00000043e+00 3.43343192e-07 9.42099456e-07 1.07137205e-06 1.90156644e-03 1.92054974e-03 4.82559113e-05 -6.43105052e-05 1.66595205e-05 1.33986604e-05 9.16890850e-02 8.20635086e-03 -1.64047654e-12 2.48779835e+00 1.00000043e+00 3.76740496e-07 1.12901501e-06 1.01496463e-06 2.07085748e-03 1.84284581e-03 -2.63780641e-04 2.16425678e-05 9.07228823e-06 -2.65046022e-05 1.91567484e-03 3.63234933e-03 -1.55820367e-12 2.33273410e+00 1.00000041e+00 3.38657590e-07 9.79796546e-07 1.04648254e-06 1.84685458e-03 1.99189484e-03 3.36520774e-05 1.02123362e-04 3.01402301e-06 6.13937003e-05 -7.84493116e-02 2.84788841e-02 -1.47800120e-12 2.62363489e+00 1.00000044e+00 3.34570581e-07 1.12580877e-06 1.08628803e-06 1.97095399e-03 2.02149469e-03 -1.94415715e-04 -1.05666747e-04 -1.70675102e-05 -5.61809635e-06 -1.81522208e-01 -5.08353286e-03
Genesis 1.3 v4¶
beam file (slice statistics)¶
input_str = P.write_genesis4_beam(
"genesis4_beam.h5", n_slice=123, verbose=True, return_input_str=True
)
Genesis4 beam file written: genesis4_beam.h5
This string is optionally returned for use in the main Genesis4 input file:
print(input_str)
&profile_file label = current xdata = genesis4_beam.h5/t ydata = genesis4_beam.h5/current isTime = T reverse = T &end &profile_file label = gamma xdata = genesis4_beam.h5/t ydata = genesis4_beam.h5/gamma isTime = T reverse = T &end &profile_file label = delgam xdata = genesis4_beam.h5/t ydata = genesis4_beam.h5/delgam isTime = T reverse = T &end &profile_file label = ex xdata = genesis4_beam.h5/t ydata = genesis4_beam.h5/ex isTime = T reverse = T &end &profile_file label = ey xdata = genesis4_beam.h5/t ydata = genesis4_beam.h5/ey isTime = T reverse = T &end &profile_file label = xcenter xdata = genesis4_beam.h5/t ydata = genesis4_beam.h5/xcenter isTime = T reverse = T &end &profile_file label = ycenter xdata = genesis4_beam.h5/t ydata = genesis4_beam.h5/ycenter isTime = T reverse = T &end &profile_file label = pxcenter xdata = genesis4_beam.h5/t ydata = genesis4_beam.h5/pxcenter isTime = T reverse = T &end &profile_file label = pycenter xdata = genesis4_beam.h5/t ydata = genesis4_beam.h5/pycenter isTime = T reverse = T &end &profile_file label = alphax xdata = genesis4_beam.h5/t ydata = genesis4_beam.h5/alphax isTime = T reverse = T &end &profile_file label = alphay xdata = genesis4_beam.h5/t ydata = genesis4_beam.h5/alphay isTime = T reverse = T &end &profile_file label = betax xdata = genesis4_beam.h5/t ydata = genesis4_beam.h5/betax isTime = T reverse = T &end &profile_file label = betay xdata = genesis4_beam.h5/t ydata = genesis4_beam.h5/betay isTime = T reverse = T &end &beam current = @current gamma = @gamma delgam = @delgam ex = @ex ey = @ey xcenter = @xcenter ycenter = @ycenter pxcenter = @pxcenter pycenter = @pycenter alphax = @alphax alphay = @alphay betax = @betax betay = @betay &end
These are the datasets written:
with File("genesis4_beam.h5", "r") as h5:
for g in h5:
print(g, len(h5[g]), h5[g].attrs["unitSymbol"])
alphax 123 1 alphay 123 1 betax 123 m betay 123 m current 123 A delgam 123 1 ex 123 m ey 123 m gamma 123 1 pxcenter 123 1 pycenter 123 1 t 123 s xcenter 123 m ycenter 123 m
Distribution file (particles)¶
P.write_genesis4_distribution("genesis4_distribution.h5", verbose=True)
Datasets x, xp, y, yp, t, p written to: genesis4_distribution.h5
This is what is written:
with File("genesis4_distribution.h5", "r") as h5:
for g in h5:
print(g, len(h5[g]))
p 10000 t 10000 x 10000 xp 10000 y 10000 yp 10000
GPT ASCII¶
P.write_gpt("gpt_particles.txt", verbose=True)
writing 10000 particles to gpt_particles.txt ASCII particles written. Convert to GDF using: asci2gdf -o particles.gdf gpt_particles.txt
if os.path.exists(os.path.expandvars("$ASCI2GDF_BIN")):
P.write_gpt("gpt_particles.gdf", verbose=True, asci2gdf_bin="$ASCI2GDF_BIN")
#!head gpt_particles.txt
Impact-T¶
Impact-T particles must all be a the same time:
P.drift_to_t(P["mean_t"])
This will return settings for Impact-T to use:
P.write_impact("impact_particles.txt")
{'input_particle_file': 'impact_particles.txt',
'Np': 10000,
'Tini': np.float64(1.8196640012738958e-14),
'Flagimg': 0}
!head impact_particles.txt
10000 -1.185035553808536871e-03 -5.962917638487320827e-04 -2.101545212762017441e-03 -5.947844736087322046e-04 6.889138455579563163e-08 2.357954834433780911e-04 -5.185459410272206951e-04 1.081304809371362412e-03 -2.178535008255477572e-03 4.729410645099751584e-04 -1.168588384170137180e-07 3.043301850869014344e-04 -1.773451522909380617e-03 -4.356182619973316826e-04 2.864977471386672206e-03 1.849365456297997709e-05 -2.599963878411861387e-08 2.261204106451414635e-04 1.686767013783681485e-03 -3.701949870666343878e-04 -2.401590848711824449e-04 9.509897711516471621e-05 -6.196091990039513892e-07 1.086073038899323766e-03 -7.779525032181033037e-04 1.314315602716767861e-04 -6.799847675989087112e-04 -4.039485790400071342e-04 -8.397600106119063685e-09 1.574553203998415077e-04 -2.593690512006365748e-03 -2.848642644110359828e-05 -2.301197068593970833e-03 4.059959321822741662e-04 -6.528501134283619379e-08 1.591207171707416379e-04 1.997801835211931304e-03 -4.156657707019708285e-06 2.648426620219629726e-03 -1.329302841047836064e-04 -4.457257395121590637e-08 5.682333568473067146e-04 1.999690961886658146e-03 -1.955217891432792742e-04 -6.946158470527301386e-04 -1.798323153729413260e-04 2.276631904252130206e-07 8.747763585337833521e-04 -7.035727003849858666e-04 1.471815598441655838e-03 -5.678538239534577621e-04 6.117313379892479327e-04 -1.922838099347932618e-08 1.486354660704120706e-04
LiTrack¶
LiTrack particles must be at the same z:
P.drift_to_z()
P.write_litrack("litrack.zd", verbose=True)
Using mean_p as the reference momentum: 441.5246616725094 eV/c writing 10000 LiTrack particles to litrack.zd
'litrack.zd'
!head -n 20 litrack.zd
% LiTrack particles % % Created using the openPMD-beamphysics Python package % https://github.com/ChristopherMayes/openPMD-beamphysics % % species: electron % n_particle: 10000 % total charge: 1.0000000000000003e-11 (C) % reference momentum p0: 441.5246616725094 (eV/c) % % Columns: ct, delta = p/p0 -1 % Units: mm, percent -2.910140500388e-01 1.222356070583e+00 3.861082943987e-01 4.105966931910e+01 1.159491741202e-01 -4.315583986424e+01 5.750254785583e-01 3.325339997689e+01 5.234406211768e-02 -4.756793241308e+01 4.093643740668e-01 -4.942448528425e+01 8.211012905157e-02 -3.245820052454e+01 -2.559578717432e-01 5.807578013130e+00
Lucretia¶
P.write_lucretia(
"lucretia.mat", ele_name="BEGINNING", t_ref=0, stop_ix=None, verbose=True
)
writing 10000 particles in the Lucretia format to lucretia.mat
Read back:
from beamphysics.interfaces.lucretia import lucretia_to_data, list_element_names
ParticleGroup(data=lucretia_to_data("lucretia.mat", verbose=True))
1 elements found in the file! 10000 particles detected, 0 found dead!
<ParticleGroup with 10000 particles at 0x34ef97827d0>
Helper function to list the available elements:
list_element_names("lucretia.mat")
['BEGINNING']
OPAL¶
Injected particled must be at the same time:
P.drift_to_t()
P.write_opal("opal_injected.txt", dist_type="injected")
!head opal_injected.txt
10000 -1.185024568260e-03 -5.962917638487e-04 -2.101534254983e-03 -5.947844736087e-04 6.454729855195e-08 2.357954834434e-04 -5.185658620169e-04 1.081304809371e-03 -2.178543721297e-03 4.729410645100e-04 -1.224655447117e-07 3.043301850869e-04 -1.773443497464e-03 -4.356182619973e-04 2.864977130676e-03 1.849365456298e-05 -3.016548095350e-08 2.261204106451e-04 1.686773833925e-03 -3.701949870666e-04 -2.401608368896e-04 9.509897711516e-05 -6.396180358817e-07 1.086073038899e-03 -7.779549245968e-04 1.314315602717e-04 -6.799773256079e-04 -4.039485790400e-04 -1.129841752215e-08 1.574553203998e-04 -2.593689987198e-03 -2.848642644110e-05 -2.301204548304e-03 4.059959321823e-04 -6.821651057540e-08 1.591207171707e-04 1.997801911791e-03 -4.156657707020e-06 2.648429069209e-03 -1.329302841048e-04 -5.504120150974e-08 5.682333568473e-04 1.999694564006e-03 -1.955217891433e-04 -6.946125339824e-04 -1.798323153729e-04 2.115470903819e-07 8.747763585338e-04 -7.035998157805e-04 1.471815598442e-03 -5.678650939367e-04 6.117313379892e-04 -2.196670599469e-08 1.486354660704e-04
Emitted particles must be at the same z:
P.drift_to_z(P["mean_z"])
P.write_opal("opal_emitted.txt", dist_type="emitted")
!head opal_emitted.txt
10000 -1.184838617375e-03 -5.962917638487e-04 -2.101348774139e-03 -5.947844736087e-04 -1.083461177889e-12 2.357954834434e-04 -5.181626563723e-04 1.081304809371e-03 -2.178367367225e-03 4.729410645100e-04 1.200565320731e-12 3.043301850869e-04 -1.773484302458e-03 -4.356182619973e-04 2.864978863003e-03 1.849365456298e-05 2.691980940714e-13 2.261204106451e-04 1.686558878409e-03 -3.701949870666e-04 -2.401056172069e-04 9.509897711516e-05 1.893601130019e-12 1.086073038899e-03 -7.779529930790e-04 1.314315602717e-04 -6.799832620349e-04 -4.039485790400e-04 5.764311716726e-15 1.574553203998e-04 -2.593700591157e-03 -2.848642644110e-05 -2.301053417928e-03 4.059959321823e-04 1.198422972634e-12 1.591207171707e-04 1.997801574883e-03 -4.156657707020e-06 2.648418294874e-03 -1.329302841048e-04 2.271058970013e-13 5.682333568473e-04 1.999743855144e-03 -1.955217891433e-04 -6.945671981683e-04 -1.798323153729e-04 -8.841733180528e-13 8.747763585338e-04 -7.034712631507e-04 1.471815598442e-03 -5.678116635530e-04 6.117313379892e-04 2.480886363183e-13 1.486354660704e-04
SIMION¶
Write SIMION input files (*.ion)
P.write_simion("simion_particles.ion")
Cleanup¶
for file in [
"astra_particles.txt",
"bmad_particles.txt",
"elegant_particles.txt",
"gpt_particles.txt",
"impact_particles.txt",
"opal_injected.txt",
"opal_emitted.txt",
"openpmd_particles.h5",
"genesis4_beam.h5",
"genesis4_distribution.h5",
"genesis2.beam",
"litrack.zd",
"gpt_particles.gdf",
"lucretia.mat",
"simion_particles.ion",
]:
if os.path.exists(file):
os.remove(file)