Converting thick to thin elements

Not all lattice elements support thick tracking and so converting these elements to thin slices is necessary before doing particle tracking or optics calculations. Elements can be converted to their thin representation using the makethin method:

>>> from particleflow.build import Lattice
>>>
>>> with Lattice({'particle': 'proton', 'beta': 0.6}) as lattice:
...     lattice.HKicker(kick=0.5, l=1.0, label='hk1')
...     lattice.Quadrupole(k1=0.625, l=5.0, label='q1')
...
>>> kicker, quad = lattice
>>> kicker
HKicker(l=tensor(1.), hkick=tensor(0.5000), vkick=tensor(0.), kick=tensor(0.5000), label='hk1')
>>> kicker.makethin(5)
ThinSegment(elements=[Drift(l=tensor(0.0833), label='hk1__d0'),
 HKicker(l=tensor(0.), hkick=tensor(0.1000), vkick=tensor(0.), kick=tensor(0.1000), label='hk1__0'),
 Drift(l=tensor(0.2083), label='hk1__d1'),
 HKicker(l=tensor(0.), hkick=tensor(0.1000), vkick=tensor(0.), kick=tensor(0.1000), label='hk1__1'),
 Drift(l=tensor(0.2083), label='hk1__d2'),
 HKicker(l=tensor(0.), hkick=tensor(0.1000), vkick=tensor(0.), kick=tensor(0.1000), label='hk1__2'),
 Drift(l=tensor(0.2083), label='hk1__d3'),
 HKicker(l=tensor(0.), hkick=tensor(0.1000), vkick=tensor(0.), kick=tensor(0.1000), label='hk1__3'),
 Drift(l=tensor(0.2083), label='hk1__d4'),
 HKicker(l=tensor(0.), hkick=tensor(0.1000), vkick=tensor(0.), kick=tensor(0.1000), label='hk1__4'),
 Drift(l=tensor(0.0833), label='hk1__d5')])

The makethin method returns a elements.ThinSegment object, a special version of a more general Segment. This ThinSegment contains the thin kicker slices as well as the drift space before, between and after the slices. The distribution of drift space depends on the selected slicing style. By default the TEAPOT 1 style is used. Other available slicing styles include SIMPLE and EDGE. For more details please consider the documentation of the elements.Element.create_thin_sequence method.

Let’s compare the SIMPLE and EDGE style for the quadrupole element:

>>> quad.makethin(5, style='edge')
ThinSegment(elements=[Drift(l=tensor(0.), label='q1__d0'),
 ThinQuadrupole(l=tensor(0.), k1l=tensor(0.6250), label='q1__0'),
 Drift(l=tensor(1.2500), label='q1__d1'),
 ThinQuadrupole(l=tensor(0.), k1l=tensor(0.6250), label='q1__1'),
 Drift(l=tensor(1.2500), label='q1__d2'),
 ThinQuadrupole(l=tensor(0.), k1l=tensor(0.6250), label='q1__2'),
 Drift(l=tensor(1.2500), label='q1__d3'),
 ThinQuadrupole(l=tensor(0.), k1l=tensor(0.6250), label='q1__3'),
 Drift(l=tensor(1.2500), label='q1__d4'),
 ThinQuadrupole(l=tensor(0.), k1l=tensor(0.6250), label='q1__4'),
 Drift(l=tensor(0.), label='q1__d5')])
>>>
>>> quad.makethin(5, style='simple')
ThinSegment(elements=[Drift(l=tensor(0.5000), label='q1__d0'),
 ThinQuadrupole(l=tensor(0.), k1l=tensor(0.6250), label='q1__0'),
 Drift(l=tensor(1.), label='q1__d1'),
 ThinQuadrupole(l=tensor(0.), k1l=tensor(0.6250), label='q1__1'),
 Drift(l=tensor(1.), label='q1__d2'),
 ThinQuadrupole(l=tensor(0.), k1l=tensor(0.6250), label='q1__2'),
 Drift(l=tensor(1.), label='q1__d3'),
 ThinQuadrupole(l=tensor(0.), k1l=tensor(0.6250), label='q1__3'),
 Drift(l=tensor(1.), label='q1__d4'),
 ThinQuadrupole(l=tensor(0.), k1l=tensor(0.6250), label='q1__4'),
 Drift(l=tensor(0.5000), label='q1__d5')])

EDGE places the outermost slices right at the edge of the thick element, while SIMPLE adds a margin that is half the in-between distance of slices.

We can also convert whole lattices represented by Segment to thin elements. Here we can choose the number of slices as well as the style via a dict which maps identifiers to the particular values. The identifiers can be strings for comparing element labels, regex patterns for matching element labels or lattice element types, similar to element selection via lattice[identifier] (see the previous sections).

>>> from particleflow.elements import HKicker, Quadrupole, Segment
>>>
>>> lattice = Segment(lattice)
>>> thin = lattice.makethin({HKicker: 2, 'q1': 5}, style={'hk1': 'edge', Quadrupole: 'simple'})
>>> thin
Segment(elements=[ThinSegment(elements=[Drift(l=tensor(0.), label='hk1__d0'),
 HKicker(l=tensor(0.), hkick=tensor(0.2500), vkick=tensor(0.), kick=tensor(0.2500), label='hk1__0'),
 Drift(l=tensor(1.), label='hk1__d1'),
 HKicker(l=tensor(0.), hkick=tensor(0.2500), vkick=tensor(0.), kick=tensor(0.2500), label='hk1__1'),
 Drift(l=tensor(0.), label='hk1__d2')]),
 ThinSegment(elements=[Drift(l=tensor(0.5000), label='q1__d0'),
 ThinQuadrupole(l=tensor(0.), k1l=tensor(0.6250), label='q1__0'),
 Drift(l=tensor(1.), label='q1__d1'),
 ThinQuadrupole(l=tensor(0.), k1l=tensor(0.6250), label='q1__1'),
 Drift(l=tensor(1.), label='q1__d2'),
 ThinQuadrupole(l=tensor(0.), k1l=tensor(0.6250), label='q1__2'),
 Drift(l=tensor(1.), label='q1__d3'),
 ThinQuadrupole(l=tensor(0.), k1l=tensor(0.6250), label='q1__3'),
 Drift(l=tensor(1.), label='q1__d4'),
 ThinQuadrupole(l=tensor(0.), k1l=tensor(0.6250), label='q1__4'),
 Drift(l=tensor(0.5000), label='q1__d5')])])

The ThinSegment`s represent their thick counterparts which are still accessible via the `.base`` attribute. Also the base label is inherited (element access works as explained in the previous sections):

>>> thin['q1'].base
Quadrupole(l=tensor(5.), k1=tensor(0.6250), label='q1')
>>> thin[1].label
'q1'
>>> for drift in thin['q1']['q1__d*']:
...     print(drift)
...
Drift(l=tensor(0.5000), label='q1__d0')
Drift(l=tensor(1.), label='q1__d1')
Drift(l=tensor(1.), label='q1__d2')
Drift(l=tensor(1.), label='q1__d3')
Drift(l=tensor(1.), label='q1__d4')
Drift(l=tensor(0.5000), label='q1__d5')

We can also flatten such a nested Segment, containing ThinSegment`s, using the `flat (or flatten) method:

>>> thin.flat()
Segment(elements=[Drift(l=tensor(0.), label='hk1__d0'),
 HKicker(l=tensor(0.), hkick=tensor(0.2500), vkick=tensor(0.), kick=tensor(0.2500), label='hk1__0'),
 Drift(l=tensor(1.), label='hk1__d1'),
 HKicker(l=tensor(0.), hkick=tensor(0.2500), vkick=tensor(0.), kick=tensor(0.2500), label='hk1__1'),
 Drift(l=tensor(0.), label='hk1__d2'),
 Drift(l=tensor(0.5000), label='q1__d0'),
 ThinQuadrupole(l=tensor(0.), k1l=tensor(0.6250), label='q1__0'),
 Drift(l=tensor(1.), label='q1__d1'),
 ThinQuadrupole(l=tensor(0.), k1l=tensor(0.6250), label='q1__1'),
 Drift(l=tensor(1.), label='q1__d2'),
 ThinQuadrupole(l=tensor(0.), k1l=tensor(0.6250), label='q1__2'),
 Drift(l=tensor(1.), label='q1__d3'),
 ThinQuadrupole(l=tensor(0.), k1l=tensor(0.6250), label='q1__3'),
 Drift(l=tensor(1.), label='q1__d4'),
 ThinQuadrupole(l=tensor(0.), k1l=tensor(0.6250), label='q1__4'),
 Drift(l=tensor(0.5000), label='q1__d5')])

flatten() returns a generator over all the nested elements.

1

H. Burkhardt, R. De Maria, M. Giovannozzi, and T. Risselada, “Improved TEAPOT Method and Tracking with Thick Quadrupoles for the LHC and its Upgrade”, in Proc. IPAC‘13, Shanghai, China, May 2013, paper MOPWO027, pp. 945-947.