Serializing lattices

We can serialize lattices into MADX scripts using the following functions from the build module:

  • create_script - Creates a full MADX script from the various sections which can be serialized with the following functions.

  • sequence_script - Serializes a lattice into a corresponding SEQUENCE; ENDSEQUENCE; block.

  • track_script - Serializes particle coordinates, plus some additional configuration, into a corrsponding TRACK; ENDTRACK; block.

  • error_script - Parses error definitions from a given lattice and serializes them into a list of SELECT and EALIGN statements.

For example:

>>> from particleflow.build import Lattice, create_script, sequence_script, track_script, error_script
>>> from particleflow.elements import Segment
>>>
>>> with Lattice(dict(particle='proton', gamma=1.25)) as lattice:
...     lattice.Quadrupole(k1=0.125, l=1, label='qf')
...     lattice.SBend(angle=0.05, l=6, label='s1')
...     lattice.Quadrupole(k1=-0.125, l=1, label='qd')
...     lattice.SBend(angle=0.05, l=6, label='s2')
...
>>> lattice = Segment(lattice)
>>> print(sequence_script(lattice))
seq: sequence, l = 14.0, refer = entry;
    qf: quadrupole, k1 = 0.125, l = 1.0, at = 0.0;
    s1: sbend, angle = 0.05, e1 = 0.0, e2 = 0.0, fint = 0.0, fintx = 0.0, hgap = 0.0, l = 6.0, at = 1.0;
    qd: quadrupole, k1 = -0.125, l = 1.0, at = 7.0;
    s2: sbend, angle = 0.05, e1 = 0.0, e2 = 0.0, fint = 0.0, fintx = 0.0, hgap = 0.0, l = 6.0, at = 8.0;
endsequence;

Now let’s create the TRACK block:

>>> import torch
>>>
>>> particles = torch.rand(6, 10)
>>> print(track_script(particles, observe=['qf', 'qd'], aperture=True, recloss=True, turns=1, maxaper=[1]*6))
track, aperture = true, recloss = true, onepass = true, dump = true, onetable = true;
    start, x = 0.48601598728336326, px = 0.956650219837559, y = 0.9888598467877321, py = 0.3745992567587124, t = 0.3191404673565723, pt = 0.8985300221575933;
    start, x = 0.8149549749594972, px = 0.4270200749373768, y = 0.635515919161222, py = 0.9159877238895374, t = 0.5733268321302711, pt = 0.11705232703701407;
    start, x = 0.7422944246905987, px = 0.13460973177940772, y = 0.9418764774837417, py = 0.8472652201346015, t = 0.9719634898137948, pt = 0.7171852345233938;
    start, x = 0.9364680812918479, px = 0.1542459780825013, y = 0.02137962413448624, py = 0.49794824430220497, t = 0.6737652915481176, pt = 0.5786539141858486;
    start, x = 0.015436083161574854, px = 0.25469968297162326, y = 0.02350832613352727, py = 0.47928000031158846, t = 0.1048411358107898, pt = 0.3429838534958689;
    start, x = 0.739526624178238, px = 0.4996576568944264, y = 0.2589179375980676, py = 0.19189491120024604, t = 0.9324930913073399, pt = 0.9043012884299407;
    start, x = 0.04371970050064822, px = 0.6079197617709056, y = 0.8672451455425605, py = 0.38988876792841975, t = 0.5096824735970663, pt = 0.8265864954728698;
    start, x = 0.4328754778996823, px = 0.45375639994207795, y = 0.7022223115273295, py = 0.050432233997635745, t = 0.26510847661524306, pt = 0.9823342991489998;
    start, x = 0.17927247316362627, px = 0.054034462012613305, y = 0.8949700770767757, py = 0.1963458427134087, t = 0.9191047099541068, pt = 0.8805875095812568;
    start, x = 0.5757753000336506, px = 0.06749373808823422, y = 0.11149076842268257, py = 0.6651908216706869, t = 0.13855791459225308, pt = 0.46272792905279236;

    observe, place = qf;
    observe, place = qd;

    run, turns = 1, maxaper = {1, 1, 1, 1, 1, 1};

    write, table = trackloss, file;
endtrack;

Let’s introduce some alignment errors to the defocusing quadrupole:

>>> from particleflow.elements import LongitudinalRoll, Offset, Tilt
>>>
>>> lattice['qd'] = Tilt(lattice['qd'], psi=0.78)  # Technically this is not an alignment error, but it does modify the element.
>>> lattice['qd'] = LongitudinalRoll(lattice['qd'], psi=0.35)
>>> lattice['qd'] = Offset(lattice['qd'], dx=0.01, dy=0.02)
>>>
>>> print(sequence_script(lattice))
seq: sequence, l = 14.0, refer = entry;
    qf: quadrupole, k1 = 0.125, l = 1.0, at = 0.0;
    s1: sbend, angle = 0.05, e1 = 0.0, e2 = 0.0, fint = 0.0, fintx = 0.0, hgap = 0.0, l = 6.0, at = 1.0;
    qd: quadrupole, k1 = -0.125, l = 1.0, tilt = 0.78, at = 7.0;
    s2: sbend, angle = 0.05, e1 = 0.0, e2 = 0.0, fint = 0.0, fintx = 0.0, hgap = 0.0, l = 6.0, at = 8.0;
endsequence;
>>>
>>> print(error_script(lattice))
eoption, add = true;
select, flag = error, clear = true;
select, flag = error, range = "qd";
ealign, dx = 0.01, dy = 0.02;
ealign, dpsi = 0.35;

Here we can see that the output from sequence_script now contains the tilt for the “qd” quadrupole and the alignment errors are summarized and assigned in the part coming from error_script.

Now let’s build the complete MADX script:

>>> print(create_script(
...     dict(particle='proton', gamma=1.25),
...     sequence=sequence_script(lattice, label='testseq'),
...     track=track_script(particles, ['qf', 'qd']),
...     errors=error_script(lattice)
... ))
beam, particle = proton, gamma = 1.25;

testseq: sequence, l = 14.0, refer = entry;
    qf: quadrupole, k1 = 0.125, l = 1.0, at = 0.0;
    s1: sbend, angle = 0.05, e1 = 0.0, e2 = 0.0, fint = 0.0, fintx = 0.0, hgap = 0.0, l = 6.0, at = 1.0;
    qd: quadrupole, k1 = -0.125, l = 1.0, tilt = 0.78, at = 7.0;
    s2: sbend, angle = 0.05, e1 = 0.0, e2 = 0.0, fint = 0.0, fintx = 0.0, hgap = 0.0, l = 6.0, at = 8.0;
endsequence;

use, sequence = testseq;

eoption, add = true;
select, flag = error, clear = true;
select, flag = error, range = "qd";
ealign, dx = 0.01, dy = 0.02;
ealign, dpsi = 0.35;

track, aperture = true, recloss = true, onepass = true, dump = true, onetable = true;
    start, x = 0.48601598728336326, px = 0.956650219837559, y = 0.9888598467877321, py = 0.3745992567587124, t = 0.3191404673565723, pt = 0.8985300221575933;
    start, x = 0.8149549749594972, px = 0.4270200749373768, y = 0.635515919161222, py = 0.9159877238895374, t = 0.5733268321302711, pt = 0.11705232703701407;
    start, x = 0.7422944246905987, px = 0.13460973177940772, y = 0.9418764774837417, py = 0.8472652201346015, t = 0.9719634898137948, pt = 0.7171852345233938;
    start, x = 0.9364680812918479, px = 0.1542459780825013, y = 0.02137962413448624, py = 0.49794824430220497, t = 0.6737652915481176, pt = 0.5786539141858486;
    start, x = 0.015436083161574854, px = 0.25469968297162326, y = 0.02350832613352727, py = 0.47928000031158846, t = 0.1048411358107898, pt = 0.3429838534958689;
    start, x = 0.739526624178238, px = 0.4996576568944264, y = 0.2589179375980676, py = 0.19189491120024604, t = 0.9324930913073399, pt = 0.9043012884299407;
    start, x = 0.04371970050064822, px = 0.6079197617709056, y = 0.8672451455425605, py = 0.38988876792841975, t = 0.5096824735970663, pt = 0.8265864954728698;
    start, x = 0.4328754778996823, px = 0.45375639994207795, y = 0.7022223115273295, py = 0.050432233997635745, t = 0.26510847661524306, pt = 0.9823342991489998;
    start, x = 0.17927247316362627, px = 0.054034462012613305, y = 0.8949700770767757, py = 0.1963458427134087, t = 0.9191047099541068, pt = 0.8805875095812568;
    start, x = 0.5757753000336506, px = 0.06749373808823422, y = 0.11149076842268257, py = 0.6651908216706869, t = 0.13855791459225308, pt = 0.46272792905279236;

    observe, place = qf;
    observe, place = qd;

    run, turns = 1, maxaper = {0.1, 0.01, 0.1, 0.01, 1.0, 0.1};

    write, table = trackloss, file;
endtrack;

In case we wanted to add optics calculations via TWISS we can just append the relevant command manually:

>>> script = create_script(dict(particle='proton', gamma=1.25), sequence=sequence_script(lattice))
>>> script += '\nselect, flag = twiss, full;\ntwiss, save, file = "twiss";'
>>> print(script)
beam, particle = proton, gamma = 1.25;

seq: sequence, l = 14.0, refer = entry;
    qf: quadrupole, k1 = 0.125, l = 1.0, at = 0.0;
    s1: sbend, angle = 0.05, e1 = 0.0, e2 = 0.0, fint = 0.0, fintx = 0.0, hgap = 0.0, l = 6.0, at = 1.0;
    qd: quadrupole, k1 = -0.125, l = 1.0, tilt = 0.78, at = 7.0;
    s2: sbend, angle = 0.05, e1 = 0.0, e2 = 0.0, fint = 0.0, fintx = 0.0, hgap = 0.0, l = 6.0, at = 8.0;
endsequence;

use, sequence = seq;
select, flag = twiss, full;
twiss, save, file = "twiss";