Serializing lattices

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

  • create_script - Creates a full MADX script including beam command and optionally error definitions as well as particle tracking.

  • 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:

[1]:
from dipas.build import Lattice, create_script, sequence_script, track_script, error_script
from dipas.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, h1 = 0.0, h2 = 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, h1 = 0.0, h2 = 0.0, hgap = 0.0, l = 6.0, at = 8.0;
endsequence;

Now let’s create the TRACK block:

[2]:
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.2014635703501506, px = 0.2662095882206157, y = 0.8166112346953612, py = 0.23180824594831628, t = 0.39202893025542407, pt = 0.5399201490740829;
    start, x = 0.14669278350197978, px = 0.24835279589455972, y = 0.27761962358694936, py = 0.8467406897590475, t = 0.9640333584721231, pt = 0.2787731815670358;
    start, x = 0.40343846523065574, px = 0.8326952974366683, y = 0.30562559574588954, py = 0.6813336597554878, t = 0.03341727991931742, pt = 0.830850622929301;
    start, x = 0.5470678008425934, px = 0.7779247137419353, y = 0.9958333764630148, py = 0.29537177766723244, t = 0.11764775607056943, pt = 0.3208520680352198;
    start, x = 0.1049864797528981, px = 0.3752285141312387, y = 0.7203908988777403, py = 0.63130467146256, t = 0.9473415670404751, pt = 0.10299125216411009;
    start, x = 0.39960444505659465, px = 0.3735462666225813, y = 0.7160529579941585, py = 0.1100296996131459, t = 0.6683040540297257, pt = 0.5498750540523619;
    start, x = 0.0228851551084277, px = 0.876894016227671, y = 0.9587570774187312, py = 0.6097960547963239, t = 0.833647004978768, pt = 0.6247855455798537;
    start, x = 0.9209046996988439, px = 0.11623483047061944, y = 0.8216139551556022, py = 0.6395245166612197, t = 0.15343851365337768, pt = 0.9395649827597847;
    start, x = 0.20836412057646447, px = 0.32546809192785775, y = 0.7780847178261877, py = 0.9400753865683356, t = 0.35463141128309483, pt = 0.10358923206153958;
    start, x = 0.4458752860950078, px = 0.19209207672465944, y = 0.43257782895074914, py = 0.027717813984675876, t = 0.5167697678230143, pt = 0.4922091373466405;

    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:

[3]:
from dipas.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, h1 = 0.0, h2 = 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, h1 = 0.0, h2 = 0.0, hgap = 0.0, l = 6.0, at = 8.0;
endsequence;

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:

[4]:
print(create_script(
    dict(particle='proton', gamma=1.25),
    sequence=lattice,
    errors=True,  # Extracts the errors from the provided `sequence`.
    track=track_script(particles, ['qf', 'qd'])
))
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, h1 = 0.0, h2 = 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, h1 = 0.0, h2 = 0.0, hgap = 0.0, l = 6.0, at = 8.0;
endsequence;

use, sequence = seq;

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.2014635703501506, px = 0.2662095882206157, y = 0.8166112346953612, py = 0.23180824594831628, t = 0.39202893025542407, pt = 0.5399201490740829;
    start, x = 0.14669278350197978, px = 0.24835279589455972, y = 0.27761962358694936, py = 0.8467406897590475, t = 0.9640333584721231, pt = 0.2787731815670358;
    start, x = 0.40343846523065574, px = 0.8326952974366683, y = 0.30562559574588954, py = 0.6813336597554878, t = 0.03341727991931742, pt = 0.830850622929301;
    start, x = 0.5470678008425934, px = 0.7779247137419353, y = 0.9958333764630148, py = 0.29537177766723244, t = 0.11764775607056943, pt = 0.3208520680352198;
    start, x = 0.1049864797528981, px = 0.3752285141312387, y = 0.7203908988777403, py = 0.63130467146256, t = 0.9473415670404751, pt = 0.10299125216411009;
    start, x = 0.39960444505659465, px = 0.3735462666225813, y = 0.7160529579941585, py = 0.1100296996131459, t = 0.6683040540297257, pt = 0.5498750540523619;
    start, x = 0.0228851551084277, px = 0.876894016227671, y = 0.9587570774187312, py = 0.6097960547963239, t = 0.833647004978768, pt = 0.6247855455798537;
    start, x = 0.9209046996988439, px = 0.11623483047061944, y = 0.8216139551556022, py = 0.6395245166612197, t = 0.15343851365337768, pt = 0.9395649827597847;
    start, x = 0.20836412057646447, px = 0.32546809192785775, y = 0.7780847178261877, py = 0.9400753865683356, t = 0.35463141128309483, pt = 0.10358923206153958;
    start, x = 0.4458752860950078, px = 0.19209207672465944, y = 0.43257782895074914, py = 0.027717813984675876, t = 0.5167697678230143, pt = 0.4922091373466405;

    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:

[5]:
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, h1 = 0.0, h2 = 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, h1 = 0.0, h2 = 0.0, hgap = 0.0, l = 6.0, at = 8.0;
endsequence;

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