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.3472782673609508, px = 0.6969169804549304, y = 0.047597093803355306, py = 0.7636730731766399, t = 0.7553138272020423, pt = 0.8292573038388925;
    start, x = 0.35734378633461317, px = 0.7038379295922748, y = 0.9048928395787847, py = 0.884623692685433, t = 0.03190009813030836, pt = 0.6780554587078552;
    start, x = 0.15759613078987877, px = 0.934656174120593, y = 0.5877146274790013, py = 0.895694200531163, t = 0.3798002928904325, pt = 0.1803614009758726;
    start, x = 0.8171910517444194, px = 0.9851717352842541, y = 0.4261080724617963, py = 0.4933171851800561, t = 0.012104312022913621, pt = 0.5870032534603374;
    start, x = 0.5539860551096644, px = 0.5089974712996393, y = 0.6301731604881488, py = 0.19108488973184956, t = 0.23377752803035956, pt = 0.31846326786532864;
    start, x = 0.36179964797332687, px = 0.5076859640326715, y = 0.4824478465564892, py = 0.9692364491374091, t = 0.15533727982745593, pt = 0.4828686941587923;
    start, x = 0.6067387851099051, px = 0.0910621153943203, y = 0.9994699802078556, py = 0.2099062668126963, t = 0.6797281102128861, pt = 0.5548304883673539;
    start, x = 0.3983796080731473, px = 0.12606531000584154, y = 0.23775429407823812, py = 0.4758817963926789, t = 0.45936994274910237, pt = 0.6189462806323894;
    start, x = 0.05188027995604649, px = 0.2941378367033436, y = 0.5573698841786207, py = 0.6880206238703079, t = 0.40916906417827514, pt = 0.7004280895717023;
    start, x = 0.6946386409209419, px = 0.18997138606539743, y = 0.30030084935382517, py = 0.13625790356429723, t = 0.5756169908605857, pt = 0.3727691782757476;

    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.3472782673609508, px = 0.6969169804549304, y = 0.047597093803355306, py = 0.7636730731766399, t = 0.7553138272020423, pt = 0.8292573038388925;
    start, x = 0.35734378633461317, px = 0.7038379295922748, y = 0.9048928395787847, py = 0.884623692685433, t = 0.03190009813030836, pt = 0.6780554587078552;
    start, x = 0.15759613078987877, px = 0.934656174120593, y = 0.5877146274790013, py = 0.895694200531163, t = 0.3798002928904325, pt = 0.1803614009758726;
    start, x = 0.8171910517444194, px = 0.9851717352842541, y = 0.4261080724617963, py = 0.4933171851800561, t = 0.012104312022913621, pt = 0.5870032534603374;
    start, x = 0.5539860551096644, px = 0.5089974712996393, y = 0.6301731604881488, py = 0.19108488973184956, t = 0.23377752803035956, pt = 0.31846326786532864;
    start, x = 0.36179964797332687, px = 0.5076859640326715, y = 0.4824478465564892, py = 0.9692364491374091, t = 0.15533727982745593, pt = 0.4828686941587923;
    start, x = 0.6067387851099051, px = 0.0910621153943203, y = 0.9994699802078556, py = 0.2099062668126963, t = 0.6797281102128861, pt = 0.5548304883673539;
    start, x = 0.3983796080731473, px = 0.12606531000584154, y = 0.23775429407823812, py = 0.4758817963926789, t = 0.45936994274910237, pt = 0.6189462806323894;
    start, x = 0.05188027995604649, px = 0.2941378367033436, y = 0.5573698841786207, py = 0.6880206238703079, t = 0.40916906417827514, pt = 0.7004280895717023;
    start, x = 0.6946386409209419, px = 0.18997138606539743, y = 0.30030084935382517, py = 0.13625790356429723, t = 0.5756169908605857, pt = 0.3727691782757476;

    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";