Line and environment

Creating a simple Line object

An Xsuite Line can be created by providing beam line element objects and the corresponding names, as illustrated in the following example:

import numpy as np
import xtrack as xt

pi = np.pi
lbend = 3

# Create an environment
env = xt.Environment()

# Build a simple ring
line = env.new_line(components=[
    env.new('mqf.1', xt.Quadrupole, length=0.3, k1=0.1),
    env.new('d1.1',  xt.Drift, length=1),
    env.new('mb1.1', xt.Bend, length=lbend, k0=pi / 2 / lbend, h=pi / 2 / lbend),
    env.new('d2.1',  xt.Drift, length=1),

    env.new('mqd.1', xt.Quadrupole, length=0.3, k1=-0.7),
    env.new('d3.1',  xt.Drift, length=1),
    env.new('mb2.1', xt.Bend, length=lbend, k0=pi / 2 / lbend, h=pi / 2 / lbend),
    env.new('d4.1',  xt.Drift, length=1),

    env.new('mqf.2', xt.Quadrupole, length=0.3, k1=0.1),
    env.new('d1.2',  xt.Drift, length=1),
    env.new('mb1.2', xt.Bend, length=lbend, k0=pi / 2 / lbend, h=pi / 2 / lbend),
    env.new('d2.2',  xt.Drift, length=1),

    env.new('mqd.2', xt.Quadrupole, length=0.3, k1=-0.7),
    env.new('d3.2',  xt.Drift, length=1),
    env.new('mb2.2', xt.Bend, length=lbend, k0=pi / 2 / lbend, h=pi / 2 / lbend),
    env.new('d4.2',  xt.Drift, length=1),
])

# Define reference particle
line.particle_ref = xt.Particles(p0c=1.2e9, mass0=xt.PROTON_MASS_EV)

# Print the line table
line.get_table().show()
# prints:
#
# name                   s element_type isthick isreplica parent_name ...
# mqf.1                  0 Quadrupole      True     False None
# d1.1                 0.3 Drift           True     False None
# mb1.1                1.3 Bend            True     False None
# d2.1                 4.3 Drift           True     False None
# etc.

# Complete source: xtrack/examples/toy_ring/000_toy_ring.py

Line inspection, Line.get_table(), Line.attr[...]

The following example illustrates how to inspect the properties of a line and its elements:

import numpy as np
import xtrack as xt

pi = np.pi
lbend = 3

# Build a simple ring
env = xt.Environment()
line = env.new_line(components=[
    env.new('mqf.1', xt.Quadrupole, length=0.3, k1=0.1),
    env.new('d1.1',  xt.Drift, length=1),
    env.new('mb1.1', xt.Bend, length=lbend, k0=pi / 2 / lbend, h=pi / 2 / lbend),
    env.new('d2.1',  xt.Drift, length=1),

    env.new('mqd.1', xt.Quadrupole, length=0.3, k1=-0.7),
    env.new('d3.1',  xt.Drift, length=1),
    env.new('mb2.1', xt.Bend, length=lbend, k0=pi / 2 / lbend, h=pi / 2 / lbend),
    env.new('d4.1',  xt.Drift, length=1),

    env.new('mqf.2', xt.Quadrupole, length=0.3, k1=0.1),
    env.new('d1.2',  xt.Drift, length=1),
    env.new('mb1.2', xt.Bend, length=lbend, k0=pi / 2 / lbend, h=pi / 2 / lbend),
    env.new('d2.2',  xt.Drift, length=1),

    env.new('mqd.2', xt.Quadrupole, length=0.3, k1=-0.7),
    env.new('d3.2',  xt.Drift, length=1),
    env.new('mb2.2', xt.Bend, length=lbend, k0=pi / 2 / lbend, h=pi / 2 / lbend),
    env.new('d4.2',  xt.Drift, length=1),
])
line.particle_ref = xt.Particles(p0c=1.2e9, mass0=xt.PROTON_MASS_EV)

# Quick access to an element and its attributes (by name)
line['mqf.1'] # is Quadrupole(length=0.3, k1=0.1, ...)
line['mqf.1'].k1 # is 0.1
line['mqf.1'].length # is 0.3

# Quick access to an element and its attributes (by index)
line[0] # is Quadrupole(length=0.3, k1=0.1, ...)
line[0].k1 # is 0.1
line[0].length # is 0.3

# Tuple with all element names
line.element_names # is ('mqf.1', 'd1.1', 'mb1.1', 'd2.1', 'mqd.1', ...

# Tuple with all element objects
line.elements # is (Quadrupole(length=0.3, k1=0.1, ...), Drift(length=1), ...

# `line.attr[...]` can be used for efficient extraction of a given attribute for
# all elements. For example:
line.attr['length'] # is (0.3, 1, 3, 1, 0.3, 1, 3, 1, 0.3, 1, 3, 1, 0.3, 1, 3, 1)
line.attr['k1l'] # is ('0.03, 0.0, 0.0, 0.0, -0.21, 0.0, 0.0, 0.0, 0.03, ... )

# The list of all attributes can be found in
line.attr.keys() # is ('length', 'k1', 'k1l', 'k2', 'k2l', 'k3', 'k3l', 'k4', ...

# `line.get_table()`` can be used to get a table with information about the line
# elements. For example:
tab = line.get_table()

# The table can be printed
tab.show()
# prints:
#
# name          s element_type isthick isreplica parent_name iscollective
# mqf.1         0 Quadrupole      True     False        None        False
# d1.1        0.3 Drift           True     False        None        False
# mb1.1       1.3 Bend            True     False        None        False
# d2.1        4.3 Drift           True     False        None        False
# mqd.1       5.3 Quadrupole      True     False        None        False
# d3.1        5.6 Drift           True     False        None        False
# mb2.1       6.6 Bend            True     False        None        False
# d4.1        9.6 Drift           True     False        None        False
# mqf.2      10.6 Quadrupole      True     False        None        False
# d1.2       10.9 Drift           True     False        None        False
# mb1.2      11.9 Bend            True     False        None        False
# d2.2       14.9 Drift           True     False        None        False
# mqd.2      15.9 Quadrupole      True     False        None        False
# d3.2       16.2 Drift           True     False        None        False
# mb2.2      17.2 Bend            True     False        None        False
# d4.2       20.2 Drift           True     False        None        False
# _end_point 21.2                False     False        None        False

# Access to a single element of the table
tab['s', 'mb2.1'] # is 6.6

# Access to a single column of the table
tab['s'] # is [0.0, 0.3, 1.3, 4.3, 5.3, 5.6, 6.6, 9.6, 10.6, 10.9, 11.9, ...

# Regular expressions can be used to select elements by name
tab.rows['mb.*']
# returns:
#
# Table: 4 rows, 94 cols
# name          s element_type isthick isreplica parent_name iscollective
# mb1.1       1.3 Bend            True     False        None        False
# mb2.1       6.6 Bend            True     False        None        False
# mb1.2      11.9 Bend            True     False        None        False
# mb2.2      17.2 Bend            True     False        None        False


# Elements can be selected by type
tab.rows[tab.element_type == 'Quadrupole']
# returns:
#
# Table: 4 rows, 94 cols
# name          s element_type isthick isreplica parent_name iscollective
# mqf.1         0 Quadrupole      True     False        None        False
# mqd.1       5.3 Quadrupole      True     False        None        False
# mqf.2      10.6 Quadrupole      True     False        None        False
# mqd.2      15.9 Quadrupole      True     False        None        False

# A section of the ring can be selected using names
tab.rows['mqd.1':'mqd.2']
# returns:
#
# Table: 9 rows, 94 cols
# name          s element_type isthick isreplica parent_name iscollective
# mqd.1       5.3 Quadrupole      True     False        None        False
# d3.1        5.6 Drift           True     False        None        False
# mb2.1       6.6 Bend            True     False        None        False
# d4.1        9.6 Drift           True     False        None        False
# mqf.2      10.6 Quadrupole      True     False        None        False
# d1.2       10.9 Drift           True     False        None        False
# mb1.2      11.9 Bend            True     False        None        False
# d2.2       14.9 Drift           True     False        None        False
# mqd.2      15.9 Quadrupole      True     False        None        False

# A section of the ring can be selected using the s coordinate
tab.rows[3.0:7.0:'s']
# returns:
#
# Table: 4 rows, 94 cols
# name         s element_type isthick isreplica parent_name iscollective
# d2.1       4.3 Drift           True     False        None        False
# mqd.1      5.3 Quadrupole      True     False        None        False
# d3.1       5.6 Drift           True     False        None        False
# mb2.1      6.6 Bend            True     False        None        False

# A section of the ring can be selected using indexes relative one element
# (e.g. to get from three elements upstream of 'mqd.1' to two elements
# downstream of 'mb2.1')
tab.rows['mqd.1<<3':'mb2.1>>2']
# returns:
#
# Table: 8 rows, 94 cols
# name          s element_type isthick isreplica parent_name iscollective
# d1.1        0.3 Drift           True     False        None        False
# mb1.1       1.3 Bend            True     False        None        False
# d2.1        4.3 Drift           True     False        None        False
# mqd.1       5.3 Quadrupole      True     False        None        False
# d3.1        5.6 Drift           True     False        None        False
# mb2.1       6.6 Bend            True     False        None        False
# d4.1        9.6 Drift           True     False        None        False
# mqf.2      10.6 Quadrupole      True     False        None        False

# Each of the selection methods above returns a valid table, hence selections
# can be chained. For example:
tab.rows[0:10:'s'].rows['mb.*']
# returns:
#
# Table: 2 rows, 94 cols
# name         s element_type isthick isreplica parent_name iscollective
# mb1.1      1.3 Bend            True     False        None        False
# mb2.1      6.6 Bend            True     False        None        False

# or more efficiently
tab.rows[0:10:'s','mb.*']
# returns:
#
# Table: 2 rows, 94 cols
# name         s element_type isthick isreplica parent_name iscollective
# mb1.1      1.3 Bend            True     False        None        False
# mb2.1      6.6 Bend            True     False        None        False


# All attributes extracted by `line.attr[...]` can be included in the table
# using `attr=True`. For example, using `tab.cols[...]` to select columns, we
# we can get the focusing strength of all quadrupoles in the ring:
tab = line.get_table(attr=True)
tab.rows[tab.element_type=='Quadrupole'].cols['s length k1l']
# returns:
#
# Table: 4 rows, 4 cols
# name          s length   k1l
# mqf.1         0    0.3  0.03
# mqd.1       5.3    0.3 -0.21
# mqf.2      10.6    0.3  0.03
# mqd.2      15.9    0.3 -0.21

# Complete source: xtrack/examples/toy_ring/004_inspect.py

References and deferred expressions

Accelerators and beam lines have complex control paterns. For example, a single high-level parameter can be used to control groups of accelerator components (e.g., sets of magnets in series, groups of RF cavities, etc.) following complex dependency relations. Xsuite allows including these dependencies in the simulation model so that changes in the high-level parameters are automatically propagated down to the line elements properties. Furthermore, the dependency relations can be created, inspected and modified at run time, as illustrated in the following example:

import numpy as np
import xtrack as xt
pi = np.pi

# We create an environment
env = xt.Environment()

# We define variables that we will use for controlling the length and the
# integrated strength of the focusing quadrupoles
env['l.quad'] = 0.3
env['k1l.qf.1'] = 0
env['k1l.qf.2'] = 0

# Expressions can be associated to any beam element property, when creating the
# element. For example:
lbend = 3
line = env.new_line(components=[
    env.new('mqf.1', xt.Quadrupole, length=0.3, k1='k1l.qf.1 / l.quad'),
    env.new('d1.1',  xt.Drift, length=1),
    env.new('mb1.1', xt.Bend, length=lbend, k0=pi / 2 / lbend, h=pi / 2 / lbend),
    env.new('d2.1',  xt.Drift, length=1),

    env.new('mqd.1', xt.Quadrupole, length=0.3, k1=0), # k1 will be set later
    env.new('d3.1',  xt.Drift, length=1),
    env.new('mb2.1', xt.Bend, length=lbend, k0=pi / 2 / lbend, h=pi / 2 / lbend),
    env.new('d4.1',  xt.Drift, length=1),

    env.new('mqf.2', xt.Quadrupole, length=0.3, k1='k1l.qf.2 / l.quad'),
    env.new('d1.2',  xt.Drift, length=1),
    env.new('mb1.2', xt.Bend, length=lbend, k0=pi / 2 / lbend, h=pi / 2 / lbend),
    env.new('d2.2',  xt.Drift, length=1),

    env.new('mqd.2', xt.Quadrupole, length=0.3, k1=0), # k1 will be set later
    env.new('d3.2',  xt.Drift, length=1),
    env.new('mb2.2', xt.Bend, length=lbend, k0=pi / 2 / lbend, h=pi / 2 / lbend),
    env.new('d4.2',  xt.Drift, length=1),
])
line.particle_ref = xt.Particles(p0c=1.2e9, mass0=xt.PROTON_MASS_EV)

# Expressions can also be assigned after the creation of the line. For example, we
# can set the integrated strength of the defocusing quadrupoles (note that line[...]
# is equivalent to env[...]):
line['k1l.qd.1'] = -0.1
line['k1l.qd.2'] = -0.1
line['mqd.1'].k1 = 'k1l.qd.1 / l.quad'
line['mqd.2'].k1 = 'k1l.qd.2 / l.quad'

# When a variable is changed, the corresponding element property is automatically
# updated:
line.vars['k1l.qf.1'] = 0.1
line['mqf.1'].k1 # is 0.333, i.e. 0.1 / lquad

# Expressions can be modified after the creation of the line. For example, we can
# create a variable controlling the integrated strength of the two focusing quadrupoles
line['k1lf'] = 0.1
line['k1l.qf.1'] = 'k1lf'
line['k1l.qf.2'] = 'k1lf'
# and a variable controlling the integrated strength of the two defocusing quadrupoles
line['k1ld'] = -0.7
line['k1l.qd.1'] = 'k1ld'
line['k1l.qd.2'] = 'k1ld'

# Changes on the controlling variable are propagated to the two controlled ones and
# to the corresponding element properties:
line['k1lf'] = 0.2
line['k1l.qf.1'] # is 0.2
line['k1l.qf.2'] # is 0.2
line['mqf.1'].k1 # is 0.666, i.e. 0.2 / lquad
line['mqf.2'].k1 # is 0.666, i.e. 0.2 / lquad

# The `info()` method of a variable provides information on the existing relations
# between the variables. For example:
line.info('k1l.qf.1')
# prints:
##  vars['k1l.qf.1']._get_value()
#   vars['k1l.qf.1'] = 0.2
#
##  vars['k1l.qf.1']._expr
#   vars['k1l.qf.1'] = vars['k1lf']
#
##  vars['k1l.qf.1']._expr._get_dependencies()
#   vars['k1lf'] = 0.2
#
##  vars['k1l.qf.1']._find_dependant_targets()
#   element_refs['mqf.1'].k1

line.info('k1lf')
# prints:
##  vars['k1lf']._get_value()
#   vars['k1lf'] = 0.2
#
##  vars['k1lf']._expr is None
#
##  vars['k1lf']._find_dependant_targets()
#   vars['k1l.qf.2']
#   element_refs['mqf.2'].k1
#   vars['k1l.qf.1']
#   element_refs['mqf.1'].k1

# The `get_info()` method of an element provides information on attribute of
# an element. For example:
line['mqf.1'].get_info('k1')
# prints:
##  element_refs['mqf.1'].k1._get_value()
#   element_refs['mqf.1'].k1 = 0.6666666666666667
#
##  element_refs['mqf.1'].k1._expr
#   element_refs['mqf.1'].k1 = (vars['k1l.qf.1'] / 0.3)
#
##  element_refs['mqf.1'].k1._expr._get_dependencies()
#   vars['k1l.qf.1'] = 0.2
#
##  element_refs['mqf.1'].k1 does not influence any target

# Expressions can include multiple variables and mathematical operations. For example
line['a'] = '3 * sqrt(k1lf) + 2 * k1ld'

# The `line.vars.get_table()` method returns a table with the value of all the
# existing variables:
line.vars.get_table()
# returns:
#
# Table: 9 rows, 2 cols
# name     value
# t_turn_s     0
# k1l.qf.1   0.2
# k1l.qd.1  -0.7
# k1l.qf.2   0.2
# k1l.qd.2  -0.7
# k1lf       0.2
# k1ld      -0.7
# a            4
# b            6

# Regular expressions can be used to select variables. For example we can select all
# the variables containing `qf` using the following:
var_tab = line.vars.get_table()
var_tab.rows['.*qf.*']
# returns:
#
# Table: 2 rows, 2 cols
# name     value
# k1l.qf.1   0.2
# k1l.qf.2   0.2

# Complete source: xtrack/examples/toy_ring/002_expressions.py

When importing a MAD-X model, the dependency relations from MAD-X deferred expressions are automatically imported as well. The following example illustrates how to inspect the dependency relations in a line imported from MAD-X:

import xtrack as xt
import xobjects as xo

from cpymad.madx import Madx

# Load sequence in MAD-X
mad = Madx()
mad.call('../../test_data/hllhc15_noerrors_nobb/sequence.madx')
mad.use(sequence="lhcb1")

# Build Xtrack line importing MAD-X expressions
line = xt.Line.from_madx_sequence(mad.sequence['lhcb1'],
                                  deferred_expressions=True # <--
                                  )
line.particle_ref = xt.Particles(mass0=xt.PROTON_MASS_EV, q0=1,
                                 gamma0=mad.sequence.lhcb1.beam.gamma)
line.build_tracker()

# MAD-X variables can be found in in the imported line. They can be
# used to change properties in the beamline.
# For example, we consider the MAD-X variable `on_x1` that controls
# the beam angle in the interaction point 1 (IP1). It is defined in
# microrad units.

# Inspect the value of the variable
line['on_x1']
# returns 1 (as defined in the import)

# Measure vertical angle at the interaction point 1 (IP1)
line.twiss()['px', 'ip1']
# ---> returns 1e-6


# Set crossing angle using the variable
line['on_x1'] = 100

# Measure vertical angle at the interaction point 1 (IP1)
print(line.twiss(at_elements=['ip1'])['px'])
# ---> returns 100e-6

# Complete source: xtrack/examples/knobs/001_lhc.py

Lattice construction capabilities

Xsuite provides several features to build lattices for beam lines and rings, which are presented in the following sections.

Role of the environment

The Xsuite environment manages variables and elements that can be shared by different lines as illustrated in the following example:

import xtrack as xt

env = xt.Environment()

# We can associate a reference particle to the environment
# which will be passed to all lines generated from this environment
env.partice_ref = xt.Particles(p0c=2e9, mass0=xt.PROTON_MASS_EV)

#############
# Variables #
#############

# The environment can be used to create and inspect variables and associated
# deferred expressions
env['a'] = 3.
env['b'] = '2 * a'

env['b'] # is 6.0
env.get_expr('b') # is (2.0 * vars['a'])
env.info('b')
# prints:
#  vars['b']._get_value()
#  vars['b'] = 6.0

#  vars['b']._expr
#  vars['b'] = (2.0 * vars['a'])

#  vars['b']._expr._get_dependencies()
#  vars['a'] = 3.0

#  vars['b'] does not influence any target

# We can use the eval method to evaluate the value for a given expression:
env.eval('sqrt(b + a)') # returns 3.0

# We can inspect the value and expression of all variables in the environment
env.vars.get_table()
# returns:
#
# Table: 3 rows, 3 cols
# name             value expr
# t_turn_s             0 None
# a                    3 None
# b                    6 (2.0 * vars['a'])

############
# Elements #
############

# The environment can be used to create and inspect elements
env.new('mq', xt.Quadrupole, length='5*a', k1='b')

env['mq'] # accesses the quadrupole

env.info('mq')
# prints:
# Element of type:  Quadrupole
# k1                  6.0                              vars['b']
# k1s                 0.0                              None
# length              15.0                             (5.0 * vars['a'])
# num_multipole_kicks 0                                None
# order               5                                None
# inv_factorial_order 0.008333333333333333             None
# knl                 [0. 0. 0. 0. 0. 0.]              None
# ...

env['mq'].length # is 15.0
env['mq'].k1 # is 6.0

env['mq'].get_expr('length') # is (5.0 * vars['a'])
env['mq'].get_info('length')
# prints:
#  element_refs['mq'].length._get_value()
#  element_refs['mq'].length = 15.0
#
#  element_refs['mq'].length._expr
#  element_refs['mq'].length = (5.0 * vars['a'])
#
#  element_refs['mq'].length._expr._get_dependencies()
#  vars['a'] = 3.0
#
#  element_refs['mq'].length does not influence any target

#########
# Lines #
#########

# The environment can be used to create and access lines

line1 = env.new_line(
    name='l1', components=['mq', env.new('dd', xt.Drift, length=10)])

line2 = env.new_line(
    name='l2', components=[
        env.new('dd', xt.Drift, length=10),
        env.new('ip', xt.Marker)])

env['l1'] # accesses the first line
env['l2'] # accesses the second line

# The environment is associated to the line
line1.env # is env
line2.env # is env

# The variables and elements of the environment are shared by all the lines
# created by the environment
line2['a'] # is 3.0
line2['a'] = 4.0
env['a'] # is 4.0

# All methods available on the environment are also available on the lines, e.g.:
line2.info('a') # is equivalent to env.info('a')

# Complete source: xtrack/examples/lattice_design/008d_environment.py

Define a line by specifying the s positions of the elements

Instead of specifying explicit drift spaces between the elements, as shown in section Creating a simple Line object, it is possible to define a line by specifying the s positions of the elements elements. When the position is not specified the element is simply placed right after the previous one. The following example illustrates this way of defining a line:

import numpy as np
import xtrack as xt

pi = np.pi
lbend = 3
l_halfcell = 5.3

# Create an environment
env = xt.Environment()

# Build a simple ring
line = env.new_line(components=[
    env.new('mqf.1', xt.Quadrupole, length=0.3, k1=0.1, at=0.15),
    env.new('d1.1',  xt.Drift, length=1),
    env.new('mb1.1', xt.Bend, length=lbend, k0=pi / 2 / lbend, h=pi / 2 / lbend),
        # drift between mb1.1 and mdq.1 defined implicitly
    env.new('mqd.1', xt.Quadrupole, length=0.3, k1=-0.7, at=l_halfcell, from_='mqf.1'),
    env.new('d3.1',  xt.Drift, length=1),
    env.new('mb2.1', xt.Bend, length=lbend, k0=pi / 2 / lbend, h=pi / 2 / lbend),
        # drift between mb2.1 and mdq.1 defined implicitly
    env.new('mqf.2', xt.Quadrupole, length=0.3, k1=0.1, at=l_halfcell, from_='mqd.1'),
    env.new('d1.2',  xt.Drift, length=1),
    env.new('mb1.2', xt.Bend, length=lbend, k0=pi / 2 / lbend, h=pi / 2 / lbend),
        # drift between mb1.2 and mdq.2 defined implicitly
    env.new('mqd.2', xt.Quadrupole, length=0.3, k1=-0.7, at=l_halfcell, from_='mqf.2'),
    env.new('d3.2',  xt.Drift, length=1),
    env.new('mb2.2', xt.Bend, length=lbend, k0=pi / 2 / lbend, h=pi / 2 / lbend),
    env.new('d4.2',  xt.Drift, length=1),
])

# Define reference particle
line.particle_ref = xt.Particles(p0c=1.2e9, mass0=xt.PROTON_MASS_EV)

# Print the line table
line.get_table().show()
# prints:
#
# name                   s element_type isthick isreplica parent_name ...
# mqf.1                  0 Quadrupole      True     False None
# d1.1                 0.3 Drift           True     False None
# mb1.1                1.3 Bend            True     False None
# d2.1                 4.3 Drift           True     False None
# etc.

# Complete source: xtrack/examples/toy_ring/000a_toy_ring_specify_s.py

Repeated elements, replicas and clones

Xsuite supports the installation of the same element multiple times in the same line. Furthermore, It is also possible to create replicas and cloneds of an element Replicas behave in the same way as repeated elements, but have a user-defined name. Element clones, instead, are full copies of the original element and inherit all properties of the parent element at the moment of clone creation, including the controlling expressions. This is illustrated in the following example:

import xtrack as xt

# We create an environment and define a quadrupole and a drift
env = xt.Environment()
env['kq'] = 0.1
env.new('mq', xt.Quadrupole, length=0.3, k1='kq')
env.new('dd', xt.Drift, length=1)

#####################
# Repeated elements #
#####################

# We create a line with repeated elements
line = env.new_line(components=['mq', 'dd', 'mq', 'dd'])

line.get_table(attr=True).cols['name s rot_s_rad']
# is:
# name                   s     rot_s_rad
# mq::0                  0             0
# dd::0                0.3             0
# mq::1                1.3             0
# dd::1                1.6             0
# _end_point           2.6             0

# Here 'mq::0' and 'mq::1' are actually the same element. Any modification on
# 'mq' is seen directly on 'mq::0' and 'mq::1'. For example, we  tilt mq by 1 mrad:
line['mq'].rot_s_rad = 1e-3

line.get_table(attr=True).cols['name s rot_s_rad']
# is:
# name                   s     rot_s_rad
# mq::0                  0         0.001
# dd::0                0.3             0
# mq::1                1.3         0.001
# dd::1                1.6             0
# _end_point           2.6             0

############
# Replicas #
############

# Replicas behave in the same way as repeated elements, but allow assigning
# a different name to each replica. For example:
env.new('my_mq_1', 'mq', mode='replica')
env.new('my_mq_2', 'mq', mode='replica')

line = env.new_line(components=['my_mq_1', 'dd', 'my_mq_2', 'dd'])

# Here 'mq_1' and 'mq::1' are actually the same element. Any modification on
# 'mq' is seen directly on 'mq::0' and 'mq::1'. For example, we  set the tilt of
# mq by 3 mrad:
line['mq'].rot_s_rad = 3e-3

line.get_table(attr=True).cols['name s rot_s_rad']
# is:
# Table: 5 rows, 3 cols
# name                   s     rot_s_rad
# my_mq_1                0         0.001
# dd::0                0.3             0
# my_mq_2              1.3         0.001
# dd::1                1.6             0
# _end_point           2.6             0

##################
# Element clones #
##################

# Element clones are different from replicas and repeated elements. They are
# actual copies of the element, which inherit the deferred expressions controlling
# their attributes from the original element. For example:

env.new('mq_clone_1', 'mq', mode='clone')
env.new('mq_clone_2', 'mq', mode='clone')

line = env.new_line(components=['mq_clone_1', 'dd', 'mq_clone_2', 'dd'])

line['mq'].get_expr('k1') # is 'kq'
line['mq_clone_1'].get_expr('k1') # is 'kq'
line['mq_clone_2'].get_expr('k1') # is 'kq'

# When changing the value of 'kq', all three elements are affected
env['kq'] = 0.2
line['mq'].k1 # is 0.2
line['mq_clone_1'].k1 # is 0.2
line['mq_clone_2'].k1 # is 0.2

# Note instead that if we alter the expression controlling the attribute of mq after
# its creation, the clones are not affected. For example:
line['mq'].k1 = '2 * kq'

line['mq'].get_expr('k1')         # is '2 * kq'
line['mq_clone_1'].get_expr('k1') # is 'kq'
line['mq_clone_2'].k1             # is 'kq'

# Clones allow for example, specifying different values for tilts and offsets
# for different elements. For example:

line['mq_clone_1'].rot_s_rad = 2e-3
line['mq_clone_2'].rot_s_rad = 3e-3

line.get_table(attr=True).cols['name s k1l rot_s_rad']
# is:
# Table: 5 rows, 4 cols
# name                   s           k1l     rot_s_rad
# mq_clone_1             0          0.03         0.002
# dd::0                0.3             0             0
# mq_clone_2           1.3          0.03         0.003
# dd::1                1.6             0             0
# _end_point           2.6             0             0

##########################################
# Replace repleted elements and replicas #
##########################################

# The line provides methods to automatically replace repeated elements and replicas
# with clones. For example:

line = env.new_line(components=['mq', 'dd', 'mq', 'dd', 'my_mq_1'])
line.get_table(attr=True).cols['name s isreplica']
# is:
# name                   s isreplica
# mq::0                  0     False
# dd::0                0.3     False
# mq::1                1.3     False
# dd::1                1.6     False
# my_mq_1              2.6      True
# _end_point           2.9     False

line.replace_all_repeated_elements()
line.replace_all_replicas()

line.get_table(attr=True).cols['name s isreplica']
# is:
# name                   s isreplica
# mq.0                   0     False
# dd.0                 0.3     False
# mq.1                 1.3     False
# dd.1                 1.6     False
# my_mq_1              2.6     False
# _end_point           2.9     False

# Complete source: xtrack/examples/toy_ring/008_repeated_elements_clones_replicas.py

Line mirroring and composition

Lines can be composed by concatenating other line. This can be simply done with the + operator. It is also possible, using the - operator to mirror a line by reversing the order of its elements. This features are particularly useful when designing beam lines, as illustrated in the following example:

import xtrack as xt
import numpy as np

env = xt.Environment()
env.particle_ref = xt.Particles(p0c=2e9, mass0=xt.PROTON_MASS_EV)
env['pi'] = np.pi

env['l_bend'] = 3.5
env['l_quad'] = 1.
env['l_cell'] = 20.
env['n_bends'] = 24.

env['h_bend']= 'pi / n_bends / l_bend'

env.new('mq', xt.Quadrupole, length='l_quad')
env.new('mb', xt.Bend, length='l_bend', h='h_bend', k0='h_bend')

env.new('mqf', 'mq', k1=0.1)
env.new('mqd', 'mq', k1=-0.1)

# We create a line which defines half of a FODO cell
arc_half_cell = env.new_line(components=[
    env.place('mqf'),
    env.place('mb', at='l_cell/4 - (l_bend/2 + 0.2)', from_='mqf'),
    env.place('mb', at='l_cell/4 + (l_bend/2 + 0.2)', from_='mqf'),
    env.place('mqd', at='l_cell/2 - l_quad/2', from_='mqf'),
    ])
arc_half_cell.survey().plot() # plots the layout of the created line
arc_half_cell.get_table()
# is:
#
# name                   s element_type isthick isreplica parent_name ...
# mqf                    0 Quadrupole      True     False None
# drift_1                1 Drift           True     False None
# mb::0                1.8 Bend            True     False None
# drift_2              5.3 Drift           True     False None
# mb::1                5.7 Bend            True     False None
# drift_3              9.2 Drift           True     False None
# mqd                  9.5 Quadrupole      True     False None
# _end_point          10.5                False     False None

# We can mirror a line by simply using the minus sign
(-arc_half_cell).get_table()
# is:
# name                   s element_type isthick isreplica parent_name ...
# mqd                    0 Quadrupole      True     False None
# drift_3                1 Drift           True     False None
# mb::0                1.3 Bend            True     False None
# drift_2              4.8 Drift           True     False None
# mb::1                5.2 Bend            True     False None
# drift_1              8.7 Drift           True     False None
# mqf                  9.5 Quadrupole      True     False None
# _end_point          10.5                False     False None

# We concatenate the two to obtain a full cell
arc_cell = -arc_half_cell + arc_half_cell
arc_cell.get_table()
# gives the following (note that the occurrence of each repeated element is
# indicated together with the element name):
#
# Table: 15 rows, 8 cols
# name                   s element_type isthick isreplica parent_name ...
# mqd::0                 0 Quadrupole      True     False None
# drift_3::0             1 Drift           True     False None
# mb::0                1.3 Bend            True     False None
# drift_2::0           4.8 Drift           True     False None
# mb::1                5.2 Bend            True     False None
# drift_1::0           8.7 Drift           True     False None
# mqf::0               9.5 Quadrupole      True     False None
# mqf::1              10.5 Quadrupole      True     False None
# drift_1::1          11.5 Drift           True     False None
# mb::2               12.3 Bend            True     False None
# drift_2::1          15.8 Drift           True     False None
# mb::3               16.2 Bend            True     False None
# drift_3::1          19.7 Drift           True     False None
# mqd::1                20 Quadrupole      True     False None
# _end_point            21                False     False None

# We create another line defining a single straight cell
straight_cell = env.new_line(components=[
    env.new('s.scell', xt.Marker), # At start cell
    env.place('mqf'),
    env.place('mqd', at='l_cell/2', from_='mqf'), # At mid cell
    env.new('e.scell', xt.Marker, at='l_cell')])

# We a ring composing arcs and straight sections
ring = 6 * (2 * arc_cell + 3 * straight_cell)

# We plot the layout of the ring
ring.survey().plot()

# Complete source: xtrack/examples/lattice_design/008a_line_composition.py

Using line replicas instead of repeated elements

It is also possible to use replicas to assemble the ring. This allows using different names for the different sections and for their elements. This is illustrated in the following example:

import xtrack as xt
import numpy as np

env = xt.Environment()
env.particle_ref = xt.Particles(p0c=2e9, mass0=xt.PROTON_MASS_EV)

env['pi'] = np.pi
env['l_bend'] = 3.5
env['l_quad'] = 1.
env['l_cell'] = 20.
env['n_bends'] = 24.

env['h_bend']= 'pi / n_bends / l_bend'

env.new('mq', xt.Quadrupole, length='l_quad')
env.new('mb', xt.Bend, length='l_bend', h='h_bend', k0='h_bend')

env.new('mqf', 'mq', k1=0.1)
env.new('mqd', 'mq', k1=-0.1)

# We create a line which defines half of a FODO cell
arc_half_cell = env.new_line(components=[
    env.place('mqf'),
    env.new('mb1', 'mb', at='l_cell/4 - (l_bend/2 + 0.2)', from_='mqf'),
    env.new('mb2', 'mb', at='l_cell/4 + (l_bend/2 + 0.2)', from_='mqf'),
    env.place('mqd', at='l_cell/2 - l_quad/2', from_='mqf'),
    ])


# We create two replicas of the half cell, one mirrored and one not and we
# call them left and right
arc_half_cell_left = env.new('l', arc_half_cell, mirror=True, mode='replica')
arc_half_cell_right = env.new('r', arc_half_cell, mode='replica')

# We concatenate the two half cells to create a full cell
cell = arc_half_cell_left + arc_half_cell_right
cell.get_table()
# this gives the following (note that the name given to the line is added to the
# element name ad suffix):
#
# name                   s element_type isthick isreplica parent_name ...
# mqd.l                  0 Quadrupole      True      True mqd
# drift_3.l              1 Drift           True      True drift_3
# mb2.l                1.3 Bend            True      True mb2
# drift_2.l            4.8 Drift           True      True drift_2
# mb1.l                5.2 Bend            True      True mb1
# drift_1.l            8.7 Drift           True      True drift_1
# mqf.l                9.5 Quadrupole      True      True mqf
# mqf.r               10.5 Quadrupole      True      True mqf
# drift_1.r           11.5 Drift           True      True drift_1
# mb1.r               12.3 Bend            True      True mb1
# drift_2.r           15.8 Drift           True      True drift_2
# mb2.r               16.2 Bend            True      True mb2
# drift_3.r           19.7 Drift           True      True drift_3
# mqd.r                 20 Quadrupole      True      True mqd
# _end_point            21                False     False None

# Similarly, we can create two cells with different names
env.new('cell1', cell, mode='replica')
env.new('cell2', cell, mode='replica')

# And we can create an arc concatenating the two cells
env['arc'] = env['cell1'] + env['cell2']

# We can make 6 arcs with different names
env.new('arc1', 'arc', mode='replica')
env.new('arc2', 'arc', mode='replica')
env.new('arc3', 'arc', mode='replica')
env.new('arc4', 'arc', mode='replica')
env.new('arc5', 'arc', mode='replica')
env.new('arc6', 'arc', mode='replica')
# Also in this case the name of the line is added as a suffix to the element
# names.

# We concatenated them in a ring. This can be done using the '+' operator
# as shown before or using the arcs as subsections of a new line, i.e.:
ring = env.new_line(components=['arc1', 'arc2', 'arc3', 'arc4', 'arc5', 'arc6'])

# One can see that having used named lines and replicas to build the ring has
# generated a regular name pattern for the elements in the ring, as we can see
# for example by listing all focusing quadrupoles in the ring:
ring.get_table().rows['mqf.*']
# is:
#
# Table: 24 rows, 8 cols
# name                         s element_type isthick isreplica ...
# mqf.l.cell1.arc1           9.5 Quadrupole      True      True
# mqf.r.cell1.arc1          10.5 Quadrupole      True      True
# mqf.l.cell2.arc1          30.5 Quadrupole      True      True
# mqf.r.cell2.arc1          31.5 Quadrupole      True      True
# mqf.l.cell1.arc2          51.5 Quadrupole      True      True
# mqf.r.cell1.arc2          52.5 Quadrupole      True      True
# mqf.l.cell2.arc2          72.5 Quadrupole      True      True
# mqf.r.cell2.arc2          73.5 Quadrupole      True      True
# mqf.l.cell1.arc3          93.5 Quadrupole      True      True
# mqf.r.cell1.arc3          94.5 Quadrupole      True      True
# mqf.l.cell2.arc3         114.5 Quadrupole      True      True
# mqf.r.cell2.arc3         115.5 Quadrupole      True      True
# etc...

# Complete source: xtrack/examples/lattice_design/008b_composition_with_replicas.py

Placing sub-lines at given s positions

As for normal elements, it is possible to place sublines at given s positions within a longer line. This is illustrated in the following example:

import xtrack as xt
import numpy as np

env = xt.Environment()
env.particle_ref = xt.Particles(p0c=2e9, mass0=xt.PROTON_MASS_EV)

sub_line = env.new_line(components=[
    env.new('mid_subline', xt.Marker, at=1.0),
    env.new('mq1_subline', xt.Quadrupole, length=0.3, at=-0.5, from_='mid_subline'),
    env.new('mq2_subline', xt.Quadrupole, length=0.3, at=0.5, from_='mid_subline')])

line = env.new_line(components=[
    env.new('bb', xt.Bend, length=1.0, at=2),
    env.place(sub_line, at=5.0),
    env.place(sub_line, at=8.0),
    ])

line.get_table()
# is:
# Table: 17 rows, 8 cols
# name                       s element_type isthick isreplica parent_name ...
# drift_4                    0 Drift           True     False None
# bb                       1.5 Bend            True     False None
# drift_5                  2.5 Drift           True     False None
# drift_1::0             4.175 Drift           True     False None
# mq1_subline::0         4.525 Quadrupole      True     False None
# drift_2::0             4.825 Drift           True     False None
# mid_subline::0         5.175 Marker         False     False None
# drift_3::0             5.175 Drift           True     False None
# mq2_subline::0         5.525 Quadrupole      True     False None
# drift_6                5.825 Drift           True     False None
# drift_1::1             7.175 Drift           True     False None
# mq1_subline::1         7.525 Quadrupole      True     False None
# drift_2::1             7.825 Drift           True     False None
# mid_subline::1         8.175 Marker         False     False None
# drift_3::1             8.175 Drift           True     False None
# mq2_subline::1         8.525 Quadrupole      True     False None
# _end_point             8.825                False     False None

# Complete source: xtrack/examples/lattice_design/008c_place_line_at_s.py

Importing a line from MAD-X

An Xsuite Line object can be imported from an existing MAD-X model, through the cpymad interface of MAD-X, using the method xtrack.Line.from_madx_sequence(). The import of certain features of the MAD-X model (dererred expressions, apertures, thick elements, alignment errors, field errors, etc.) can be controlled by the user. This is illustrated in the following example:

import numpy as np
from cpymad.madx import Madx

import xtrack as xt

import matplotlib.pyplot as plt

from cpymad.madx import Madx

mad = Madx()

# Load mad model and apply element shifts
mad.input('''
call, file = '../../test_data/psb_chicane/psb.seq';
call, file = '../../test_data/psb_chicane/psb_fb_lhc.str';

beam, particle=PROTON, pc=0.5708301551893517;
use, sequence=psb1;

select,flag=error,clear;
select,flag=error,pattern=bi1.bsw1l1.1*;
ealign, dx=-0.0057;

select,flag=error,clear;
select,flag=error,pattern=bi1.bsw1l1.2*;
select,flag=error,pattern=bi1.bsw1l1.3*;
select,flag=error,pattern=bi1.bsw1l1.4*;
ealign, dx=-0.0442;

twiss;
''')

line = xt.Line.from_madx_sequence(
    sequence=mad.sequence.psb1,
    allow_thick=True,
    enable_align_errors=True,
    deferred_expressions=True,
)
line.particle_ref = xt.Particles(mass0=xt.PROTON_MASS_EV,
                            gamma0=mad.sequence.psb1.beam.gamma)

# Complete source: xtrack/examples/psb/000a_all_xsuite_import_model.py

Save and reload lines

An Xtrack Line object can be transformed into a dictionary or saved to a json file, as illustrated in the following example:

import json

import numpy as np

import xtrack as xt
import xobjects as xo

env = xt.Environment()

# Build a beam line
line = env.new_line(components=[
    env.new('m1', xt.Multipole, knl=np.array([1.,2.,3.])),
    env.new('d1', xt.Drift, length=2.),
    env.new('c1', xt.Cavity, frequency=400e9, voltage=1e6),
    env.new('m2', xt.Multipole, knl=np.array([1.,2.,3.])),
    env.new('c2', xt.Drift, length=2.),
    ],
)

# Save to json
line.to_json('line.json')

# Load from json
line_2 = xt.Line.from_json('line.json')

# Alternatively the to_dict method can be used, which is more flexible for
# example to save additional information in the json file

#Save
dct = line.to_dict()
dct['my_additional_info'] = 'Important information'
with open('line.json', 'w') as fid:
    json.dump(dct, fid, cls=xo.JEncoder)

# Load
with open('line.json', 'r') as fid:
    loaded_dct = json.load(fid)
line_2 = xt.Line.from_dict(loaded_dct)
# loaded_dct['my_additional_info'] contains "Important information"

# Complete source: xtrack/examples/to_json/000_lattice_to_json.py

Element insertion

import numpy as np
import xtrack as xt

pi = np.pi
lbend = 3

# Create an environment
env = xt.Environment()

# Build a simple ring
line = env.new_line(components=[
    env.new('mqf.1', xt.Quadrupole, length=0.3, k1=0.1),
    env.new('d1.1',  xt.Drift, length=1),
    env.new('mb1.1', xt.Bend, length=lbend, k0=pi / 2 / lbend, h=pi / 2 / lbend),
    env.new('d2.1',  xt.Drift, length=1),

    env.new('mqd.1', xt.Quadrupole, length=0.3, k1=-0.7),
    env.new('d3.1',  xt.Drift, length=1),
    env.new('mb2.1', xt.Bend, length=lbend, k0=pi / 2 / lbend, h=pi / 2 / lbend),
    env.new('d4.1',  xt.Drift, length=1),

    env.new('mqf.2', xt.Quadrupole, length=0.3, k1=0.1),
    env.new('d1.2',  xt.Drift, length=1),
    env.new('mb1.2', xt.Bend, length=lbend, k0=pi / 2 / lbend, h=pi / 2 / lbend),
    env.new('d2.2',  xt.Drift, length=1),

    env.new('mqd.2', xt.Quadrupole, length=0.3, k1=-0.7),
    env.new('d3.2',  xt.Drift, length=1),
    env.new('mb2.2', xt.Bend, length=lbend, k0=pi / 2 / lbend, h=pi / 2 / lbend),
    env.new('d4.2',  xt.Drift, length=1),
])

# Build the ring
line.particle_ref = xt.Particles(p0c=1.2e9, mass0=xt.PROTON_MASS_EV)
line.build_tracker()

# Inspect the line
tab = line.get_table()
tab.show()
# prints:
#
# name          s element_type isthick isreplica parent_name iscollective
# mqf.1         0 Quadrupole      True     False        None        False
# d1.1        0.3 Drift           True     False        None        False
# mb1.1       1.3 Bend            True     False        None        False
# d2.1        4.3 Drift           True     False        None        False
# mqd.1       5.3 Quadrupole      True     False        None        False
# d3.1        5.6 Drift           True     False        None        False
# mb2.1       6.6 Bend            True     False        None        False
# d4.1        9.6 Drift           True     False        None        False
# mqf.2      10.6 Quadrupole      True     False        None        False
# d1.2       10.9 Drift           True     False        None        False
# mb1.2      11.9 Bend            True     False        None        False
# d2.2       14.9 Drift           True     False        None        False
# mqd.2      15.9 Quadrupole      True     False        None        False
# d3.2       16.2 Drift           True     False        None        False
# mb2.2      17.2 Bend            True     False        None        False
# d4.2       20.2 Drift           True     False        None        False
# _end_point 21.2                False     False        None        False

# Define a sextupole
my_sext = xt.Sextupole(length=0.1, k2=0.1)
# Insert copies of the defined sextupole downstream of the quadrupoles
line.discard_tracker() # needed to modify the line structure
line.insert_element('msf.1', my_sext.copy(), at_s=tab['s', 'mqf.1'] + 0.4)
line.insert_element('msd.1', my_sext.copy(), at_s=tab['s', 'mqd.1'] + 0.4)
line.insert_element('msf.2', my_sext.copy(), at_s=tab['s', 'mqf.2'] + 0.4)
line.insert_element('msd.2', my_sext.copy(), at_s=tab['s', 'mqd.2'] + 0.4)

# Define a rectangular aperture
my_aper = xt.LimitRect(min_x=-0.02, max_x=0.02, min_y=-0.01, max_y=0.01)
# Insert the aperture upstream of the first bending magnet
line.insert_element('aper', my_aper, index='mb1.1')

line.get_table().show()
# prints:
#
# name          s element_type isthick isreplica parent_name iscollective
# mqf.1         0 Quadrupole      True     False        None        False
# d1.1..0     0.3 DriftSlice      True     False        d1.1        False
# msf.1       0.4 Sextupole       True     False        None        False
# d1.1..2     0.5 DriftSlice      True     False        d1.1        False
# aper        1.3 LimitRect      False     False        None        False
# mb1.1       1.3 Bend            True     False        None        False
# d2.1        4.3 Drift           True     False        None        False
# mqd.1       5.3 Quadrupole      True     False        None        False
# d3.1..0     5.6 DriftSlice      True     False        d3.1        False
# msd.1       5.7 Sextupole       True     False        None        False
# d3.1..2     5.8 DriftSlice      True     False        d3.1        False
# mb2.1       6.6 Bend            True     False        None        False
# d4.1        9.6 Drift           True     False        None        False
# mqf.2      10.6 Quadrupole      True     False        None        False
# d1.2..0    10.9 DriftSlice      True     False        d1.2        False
# msf.2        11 Sextupole       True     False        None        False
# d1.2..2    11.1 DriftSlice      True     False        d1.2        False
# mb1.2      11.9 Bend            True     False        None        False
# d2.2       14.9 Drift           True     False        None        False
# mqd.2      15.9 Quadrupole      True     False        None        False
# d3.2..0    16.2 DriftSlice      True     False        d3.2        False
# msd.2      16.3 Sextupole       True     False        None        False
# d3.2..2    16.4 DriftSlice      True     False        d3.2        False
# mb2.2      17.2 Bend            True     False        None        False
# d4.2       20.2 Drift           True     False        None        False
# _end_point 21.2                False     False        None        False

# Complete source: xtrack/examples/toy_ring/005_insert_element.py

Element slicing

It is possible to slice thick element with thin or thick slices, using the Uniform or the Teapot scheme. This is illustrated in the following example:

import numpy as np
import xtrack as xt

# We build a simple ring
pi = np.pi
lbend = 3
lquad = 0.3
env = xt.Environment()
line = env.new_line(components=[
    env.new('mqf.1', xt.Quadrupole, length=lquad, k1=0.1),
    env.new('msf.1', xt.Sextupole, length=0.1, k2=0.02),
    env.new('d1.1',  xt.Drift, length=0.9),
    env.new('mb1.1', xt.Bend, length=lbend, k0=pi / 2 / lbend, h=pi / 2 / lbend),
    env.new('d2.1',  xt.Drift, length=1),

    env.new('mqd.1', xt.Quadrupole, length=lquad, k1=-0.7),
    env.new('d3.1',  xt.Drift, length=1),
    env.new('mb2.1', xt.Bend, length=lbend, k0=pi / 2 / lbend, h=pi / 2 / lbend),
    env.new('d4.1',  xt.Drift, length=1),

    env.new('mqf.2', xt.Quadrupole, length=lquad, k1=0.1),
    env.new('d1.2',  xt.Drift, length=1),
    env.new('mb1.2', xt.Bend, length=lbend, k0=pi / 2 / lbend, h=pi / 2 / lbend),
    env.new('d2.2',  xt.Drift, length=1),

    env.new('mqd.2', xt.Quadrupole, length=lquad, k1=-0.7),
    env.new('d3.2',  xt.Drift, length=1),
    env.new('mb2.2', xt.Bend, length=lbend, k0=pi / 2 / lbend, h=pi / 2 / lbend),
    env.new('d4.2',  xt.Drift, length=1),
])
line.particle_ref = xt.Particles(p0c=1.2e9, mass0=xt.PROTON_MASS_EV)

line_before_slicing = line.copy() # Keep for comparison

# Slice different elements with different strategies (in case multiple strategies
# apply to the same element, the last one takes precedence)
line.slice_thick_elements(
    slicing_strategies=[
        # Slicing with thin elements
        xt.Strategy(slicing=xt.Teapot(1)), # (1) Default applied to all elements
        xt.Strategy(slicing=xt.Uniform(2), element_type=xt.Bend), # (2) Selection by element type
        xt.Strategy(slicing=xt.Teapot(3), element_type=xt.Quadrupole),  # (4) Selection by element type
        xt.Strategy(slicing=xt.Teapot(4), name='mb1.*'), # (5) Selection by name pattern
        # Slicing with thick elements
        xt.Strategy(slicing=xt.Uniform(2, mode='thick'), name='mqf.*'), # (6) Selection by name pattern
        # Do not slice (leave untouched)
        xt.Strategy(slicing=None, name='mqd.1') # (7) Selection by name
    ])
line.build_tracker()
line_before_slicing.build_tracker()

# Ispect the result:

ltable = line.get_table(attr=True).cols['s', 'isthick', 'element_type',
                                        'parent_name', 'k0l', 'k1l', 'k2l']

# The sextupole msf.1 has one thin slice, as default strategy (1) is applied.
ltable.rows['msf.1_entry':'msf.1_exit']
# returns:
#
# Table: 5 rows, 8 cols
# name                s isthick element_type         parent_name k0l k1l   k2l
# msf.1_entry       0.3   False Marker                      None   0   0     0
# drift_msf.1..0    0.3    True DriftSliceSextupole        msf.1   0   0     0
# msf.1..0         0.35   False ThinSliceSextupole         msf.1   0   0 0.002
# drift_msf.1..1   0.35    True DriftSliceSextupole        msf.1   0   0     0
# msf.1_exit        0.4   False Marker                      None   0   0     0

# The bend mb2.1 has three thin slices, as strategy (2) is applied.
ltable.rows['mb2.1_entry':'mb2.1_exit']
# returns:
#
# Table: 9 rows, 8 cols
# name               s isthick element_type         parent_name      k0l k1l k2l
# mb2.1_entry      6.6   False Marker                      None        0   0   0
# mb2.1..entry_map 6.6   False ThinSliceBendEntry         mb2.1        0   0   0
# drift_mb2.1..0   6.6    True DriftSliceBend             mb2.1        0   0   0
# mb2.1..0         7.6   False ThinSliceBend              mb2.1 0.785398   0   0
# drift_mb2.1..1   7.6    True DriftSliceBend             mb2.1        0   0   0
# mb2.1..1         8.6   False ThinSliceBend              mb2.1 0.785398   0   0
# drift_mb2.1..2   8.6    True DriftSliceBend             mb2.1        0   0   0
# mb2.1..exit_map  9.6   False ThinSliceBendExit          mb2.1        0   0   0
# mb2.1_exit       9.6   False Marker                      None        0   0   0

# The quadrupole mqd.2 has four thin slices, as strategy (3) is applied.
ltable.rows['mqd.2_entry':'mqd.2_exit']
# returns:
#
# Table: 9 rows, 8 cols
# name                   s isthick element_type         parent_name k0l   k1l ...
# mqd.2_entry         15.9   False Marker                      None   0     0
# drift_mqd.2..0      15.9    True DriftSliceQuadrupole       mqd.2   0     0
# mqd.2..0         15.9375   False ThinSliceQuadrupole        mqd.2   0 -0.07
# drift_mqd.2..1   15.9375    True DriftSliceQuadrupole       mqd.2   0     0
# mqd.2..1           16.05   False ThinSliceQuadrupole        mqd.2   0 -0.07
# drift_mqd.2..2     16.05    True DriftSliceQuadrupole       mqd.2   0     0
# mqd.2..2         16.1625   False ThinSliceQuadrupole        mqd.2   0 -0.07
# drift_mqd.2..3   16.1625    True DriftSliceQuadrupole       mqd.2   0     0
# mqd.2_exit          16.2   False Marker                      None   0     0

# The quadrupole mqf.1 has two thick slices, as strategy (6) is applied.
ltable.rows['mqf.1_entry':'mqf.1_exit']
# returns:
#
# Table: 4 rows, 8 cols
# name                s isthick element_type         parent_name k0l   k1l k2l
# mqf.1_entry         0   False Marker                      None   0     0   0
# mqf.1..0            0    True ThickSliceQuadrupole       mqf.1   0 0.015   0
# mqf.1..1         0.15    True ThickSliceQuadrupole       mqf.1   0 0.015   0
# mqf.1_exit        0.3   False Marker                      None   0     0   0

# The quadrupole mqd.1 is left untouched, as strategy (7) is applied.
ltable.rows['mqd.1']
# returns:
#
# Table: 1 row, 8 cols
# name               s isthick element_type         parent_name k0l   k1l k2l
# mqd.1            5.3    True Quadrupole                  None   0 -0.21   0


########################################
# Change properties of sliced elements #
########################################

# Sliced elements are updated whenever their parent is changed. For example:

# Inspect a quadrupole:
ltable.rows['mqf.1.*']
# returns:
#
# Table: 4 rows, 8 cols
# name                s isthick element_type         parent_name k0l   k1l k2l
# mqf.1_entry         0   False Marker                      None   0     0   0
# mqf.1..0            0    True ThickSliceQuadrupole       mqf.1   0 0.015   0
# mqf.1..1         0.15    True ThickSliceQuadrupole       mqf.1   0 0.015   0
# mqf.1_exit        0.3   False Marker                      None   0     0   0

# Change the the strength of the parent
line['mqf.1'].k1 = 0.2

# Inspect
ltable = line.get_table(attr=True).cols['s', 'isthick', 'element_type',
                                        'parent_name', 'k0l', 'k1l', 'k2l']
ltable.rows['mqf.1.*']
# returns (the strength of the slices has changed):
#
# Table: 4 rows, 8 cols
# name                s isthick element_type         parent_name k0l  k1l k2l
# mqf.1_entry         0   False Marker                      None   0    0   0
# mqf.1..0            0    True ThickSliceQuadrupole       mqf.1   0 0.03   0
# mqf.1..1         0.15    True ThickSliceQuadrupole       mqf.1   0 0.03   0
# mqf.1_exit        0.3   False Marker                      None   0    0   0

# Complete source: xtrack/examples/toy_ring/003_slicing.py

Simulation of small rings: drifts, bends, fringe fields

The modeling of the body of bending magnets in automatically adapted depending on the bending radius, hence no special setting is required for this purpose when simulating small rings with large bending angles.

However, the modeling of the fringe fields and the drifts is not automatically adapted and appropriate settings need to be provided by the user.

The following example illustrates how to switch to the full model for the fringe fields and the drifts and compares the effect of different models on the optics functions and the chromatic properties of the CERN ELENA ring:

import numpy as np
from cpymad.madx import Madx
import xtrack as xt

# We get the model from MAD-X
mad = Madx()
folder = ('../../test_data/elena')
mad.call(folder + '/elena.seq')
mad.call(folder + '/highenergy.str')
mad.call(folder + '/highenergy.beam')
mad.use('elena')

# Build xsuite line
seq = mad.sequence.elena
line = xt.Line.from_madx_sequence(seq)
line.particle_ref = xt.Particles(gamma0=seq.beam.gamma,
                                    mass0=seq.beam.mass * 1e9,
                                    q0=seq.beam.charge)

# Inspect one bend
line['lnr.mbhek.0135']
# returns:
#
# Bend(length=0.971, k0=1.08, k1=0, h=1.08, model='adaptive',
#      knl=array([0., 0., 0., 0., 0.]), ksl=array([0., 0., 0., 0., 0.]),
#      edge_entry_active=1, edge_exit_active=1,
#      edge_entry_model='linear', edge_exit_model='linear',
#      edge_entry_angle=0.287, edge_exit_angle=0.287,
#      edge_entry_angle_fdown=0, edge_exit_angle_fdown=0,
#      edge_entry_fint=0.424, edge_exit_fint=0.424,
#      edge_entry_hgap=0.038, edge_exit_hgap=0.038,
#      shift_x=0, shift_y=0, rot_s_rad=0)

# By default the adaptive model is used for the core and the linearized model for the edge
line['lnr.mbhek.0135'].model # is 'adaptive'
line['lnr.mbhek.0135'].edge_entry_model # is 'linear'
line['lnr.mbhek.0135'].edge_exit_model # is 'linear'

# For small machines (bends with large bending angles) it is more appropriate to
# switch to the `full` model for the edge
line.configure_bend_model(core='adaptive', edge='full')

# It is also possible to switch from the expanded drift to the exact one
line.config.XTRACK_USE_EXACT_DRIFTS = True

line['lnr.mbhek.0135'].model # is 'adaptive'
line['lnr.mbhek.0135'].edge_entry_model # is 'full'
line['lnr.mbhek.0135'].edge_exit_model # is 'full'

# Slice the bends to see the behavior of the optics functions within them
line.slice_thick_elements(
    slicing_strategies=[
        xt.Strategy(slicing=None), # don't touch other elements
        xt.Strategy(slicing=xt.Uniform(10, mode='thick'), element_type=xt.Bend)
    ])

# Twiss
tw = line.twiss(method='4d')

# Switch to a simplified model
line.configure_bend_model(core='expanded', edge='linear')
line.config.XTRACK_USE_EXACT_DRIFTS = False

# Twiss with the default model
tw_simpl = line.twiss(method='4d')

# Compare beta functions and chromatic properties

import matplotlib.pyplot as plt
plt.close('all')
plt.figure(1, figsize=(6.4, 4.8 * 1.5))
ax1 = plt.subplot(4,1,1)
plt.plot(tw.s, tw.betx, label='adaptive')
plt.plot(tw_simpl.s, tw_simpl.betx, '--', label='simplified')
plt.ylabel(r'$\beta_x$')
plt.legend(loc='best')

ax2 = plt.subplot(4,1,2, sharex=ax1)
plt.plot(tw.s, tw.bety)
plt.plot(tw_simpl.s, tw_simpl.bety, '--')
plt.ylabel(r'$\beta_y$')

ax3 = plt.subplot(4,1,3, sharex=ax1)
plt.plot(tw.s, tw.wx_chrom)
plt.plot(tw_simpl.s, tw_simpl.wx_chrom, '--')
plt.ylabel(r'$W_x$')

ax4 = plt.subplot(4,1,4, sharex=ax1)
plt.plot(tw.s, tw.wy_chrom)
plt.plot(tw_simpl.s, tw_simpl.wy_chrom, '--')
plt.ylabel(r'$W_y$')
plt.xlabel('s [m]')

# Highlight the bends
tt_sliced = line.get_table()
tbends = tt_sliced.rows[tt_sliced.element_type == 'ThickSliceBend']
for ax in [ax1, ax2, ax3, ax4]:
    for nn in tbends.name:
        ax.axvspan(tbends['s', nn], tbends['s', nn] + line[nn].length,
                   color='b', alpha=0.2, linewidth=0)

plt.show()

# Complete source: xtrack/examples/small_rings/000_elena_chromatic_functions.py
_images/elena_w_chrom.png

Comparison of the simplified and full model for the CERN ELENA ring (the six bends of the ring are highlighted in blue). While the linear optics is well reproduced by the simplified model, the chromatic properties differ significantly (in particular, note the effect of the dipole edges).

Extraction of second order transfer maps

The method xtrack.Line.get_line_with_second_order_maps() allows modeling portions of a beam line with second order transfer maps. This is illustrated in the following example.

See also xtrack.SecondOrderTaylorMap.from_line().

import numpy as np
import xtrack as xt

# Get a line and build a tracker
line = xt.Line.from_json('../../test_data/hllhc15_thick/lhc_thick_with_knobs.json')
line.build_tracker()

# Switch RF on
line.vars['vrf400'] = 16
line.vars['lagrf400.b1'] = 0.5

# Enable crossing angle orbit bumps
line.vars['on_x1'] = 10
line.vars['on_x2'] = 20
line.vars['on_x5'] = 10
line.vars['on_x8'] = 30

# Generate line made on maps (splitting at defined markers)
ele_cut = ['ip1', 'ip2', 'ip5', 'ip8'] # markers where to split the line
line_maps = line.get_line_with_second_order_maps(split_at=ele_cut)
line_maps.build_tracker()

line_maps.get_table().show()
# prints:
#
# name              s element_type         isthick
# ip7               0 Marker                 False
# map_0             0 SecondOrderTaylorMap    True
# ip8         3321.22 Marker                 False
# map_1       3321.22 SecondOrderTaylorMap    True
# ip1         6664.72 Marker                 False
# map_2       6664.72 SecondOrderTaylorMap    True
# ip2         9997.16 Marker                 False
# map_3       9997.16 SecondOrderTaylorMap    True
# ip5           19994 Marker                 False
# map_4         19994 SecondOrderTaylorMap    True
# lhcb1ip7_p_ 26658.9 Marker                 False
# _end_point  26658.9                        False

# Compare twiss of the two lines
tw = line.twiss()
tw_map = line_maps.twiss()

tw.qx       # is 62.3099999
tw_map.qx   # is  0.3099999

tw.dqx      # is 1.9135
tw_map.dqx  # is 1.9137

tw.rows[['ip1', 'ip2', 'ip5', 'ip8']].cols['x px y py betx bety'].show()
# prints
#
# name           x          px           y          py    betx    bety
# ip8  9.78388e-09 3.00018e-05 6.25324e-09 5.47624e-09 1.50001 1.49999
# ip1  1.92278e-10 1.00117e-05 2.64869e-09  1.2723e-08    0.15    0.15
# ip2  1.47983e-08 2.11146e-09 -2.1955e-08 2.00015e-05      10 9.99997
# ip5 -3.03593e-09 5.75413e-09 5.13184e-10  1.0012e-05    0.15    0.15

tw_map.rows[['ip1', 'ip2', 'ip5', 'ip8']].cols['x px y py betx bety']
# prints
#
# name             x          px           y          py    betx    bety
# ip8    9.78388e-09 3.00018e-05 6.25324e-09 5.47624e-09 1.50001 1.49999
# ip1    1.92278e-10 1.00117e-05 2.64869e-09  1.2723e-08    0.15    0.15
# ip2    1.47983e-08 2.11146e-09 -2.1955e-08 2.00015e-05      10 9.99997
# ip5   -3.03593e-09 5.75413e-09 5.13184e-10  1.0012e-05    0.15    0.15

# Complete source: xtrack/examples/taylor_map/000_line_with_maps.py

Apply transformations (tilt, shift) to elements

Tilt and shifts transformations can be applied to beam elements, as illustrated in the following example:

import xtrack as xt
from cpymad.madx import Madx

# Load a very simple sequence from MAD-X
mad = Madx()
mad.input("""
    seq: sequence, l=4;
    b1: sbend, at=0.5, angle=0.2, l=1;
    b2: sbend, at=2.5, angle=0.3, l=1;
    endsequence;

    beam;
    use,sequence=seq;
""")

line = xt.Line.from_madx_sequence(mad.sequence.seq)
line.build_tracker()

print('The line as imported from MAD-X:')
line.get_table().show()

# Shift and tilt selected elements
line['b1'].shift_x = -0.01
line['b1'].rot_s_rad = 0.8
line['b2'].shift_s = 0.02
line['b2'].rot_s_rad = -0.8

tt = line.get_table(attr=True)
tt.cols['s', 'element_type', 'isthick', 'shift_x', 'shift_y', 'shift_s', 'rot_s_rad']
# returns:
#
# name       s element_type isthick shift_x shift_y shift_s rot_s_rad
# seq$start  0 Marker         False       0       0       0         0
# b1         0 Bend            True   -0.01       0       0       0.8
# drift_0    1 Drift           True       0       0       0         0
# b2         2 Bend            True       0       0    0.02      -0.8
# drift_1    3 Drift           True       0       0       0         0
# seq$end    4 Marker         False       0       0       0         0
# _end_point 4                False       0       0       0         0

# Complete source: xtrack/examples/element_transformations/000_element_transform.py

Tranfromations are propagated when the elements are sliced and can be updated also after the slicing by acting on the parent element. This is illustrated in the following example:

import xtrack as xt
from cpymad.madx import Madx

# Load a very simple sequence from MAD-X
mad = Madx()
mad.input("""
    seq: sequence, l=4;
    b1: sbend, at=0.5, angle=0.2, l=1;
    b2: sbend, at=2.5, angle=0.3, l=1;
    endsequence;

    beam;
    use,sequence=seq;
""")

line = xt.Line.from_madx_sequence(mad.sequence.seq)
line.build_tracker()

print('The line as imported from MAD-X:')
print(line.get_table())

# Shift and tilt selected elements
line['b1'].shift_x = -0.1
line['b1'].rot_s_rad = 0.8
line['b2'].shift_s= 0.2
line['b2'].rot_s_rad = -0.8

tt = line.get_table(attr=True)
tt.cols['s', 'element_type', 'isthick', 'shift_x', 'shift_y', 'shift_s', 'rot_s_rad']
# returns:
#
# name       s element_type isthick shift_x shift_y shift_s rot_s_rad
# seq$start  0 Marker         False       0       0       0         0
# b1         0 Bend            True    -0.1       0       0       0.8
# drift_0    1 Drift           True       0       0       0         0
# b2         2 Bend            True       0       0     0.2      -0.8
# drift_1    3 Drift           True       0       0       0         0
# seq$end    4 Marker         False       0       0       0         0
# _end_point 4                False       0       0       0         0

# Slice the line
slicing_strategies = [
    xt.Strategy(slicing=None),  # Default catch-all
    xt.Strategy(slicing=xt.Teapot(2), element_type=xt.Bend),
]
line.slice_thick_elements(slicing_strategies)
line.build_tracker()

# Inspect
tt = line.get_table(attr=True)
tt.cols['s', 'element_type', 'isthick', 'parent_name',
        'shift_x', 'shift_y', 'shift_s', 'rot_s_rad']
# returns:
#
# Table: 23 rows, 9 cols
# name                 s element_type       isthick parent_name shift_x shift_y shift_s rot_s_rad
# seq$start            0 Marker               False        None       0       0       0         0
# b1_entry             0 Marker               False        None       0       0       0         0
# b1..entry_map        0 ThinSliceBendEntry   False          b1    -0.1       0       0       0.8
# drift_b1..0          0 DriftSliceBend        True          b1       0       0       0         0
# b1..0         0.166667 ThinSliceBend        False          b1    -0.1       0       0       0.8
# drift_b1..1   0.166667 DriftSliceBend        True          b1       0       0       0         0
# b1..1         0.833333 ThinSliceBend        False          b1    -0.1       0       0       0.8
# drift_b1..2   0.833333 DriftSliceBend        True          b1       0       0       0         0
# b1..exit_map         1 ThinSliceBendExit    False          b1    -0.1       0       0       0.8
# b1_exit              1 Marker               False        None       0       0       0         0
# drift_0              1 Drift                 True        None       0       0       0         0
# b2_entry             2 Marker               False        None       0       0       0         0
# b2..entry_map        2 ThinSliceBendEntry   False          b2       0       0     0.2      -0.8
# drift_b2..0          2 DriftSliceBend        True          b2       0       0       0        -0
# b2..0          2.16667 ThinSliceBend        False          b2       0       0     0.2      -0.8
# drift_b2..1    2.16667 DriftSliceBend        True          b2       0       0       0        -0
# b2..1          2.83333 ThinSliceBend        False          b2       0       0     0.2      -0.8
# drift_b2..2    2.83333 DriftSliceBend        True          b2       0       0       0        -0
# b2..exit_map         3 ThinSliceBendExit    False          b2       0       0     0.2      -0.8
# b2_exit              3 Marker               False        None       0       0       0         0
# drift_1              3 Drift                 True        None       0       0       0         0
# seq$end              4 Marker               False        None       0       0       0         0
# _end_point           4                      False        None       0       0       0         0


# Update misalignment for one element. We act on the parent and the effect is
# propagated to the slices.
line['b2'].rot_s_rad = 0.3
line['b2'].shift_x = 2e-3

# Inspect
tt = line.get_table(attr=True)
tt.cols['s', 'element_type', 'isthick', 'parent_name', 'shift_x', 'shift_y', 'rot_s_rad']
# returns:
#
# Table: 23 rows, 8 cols
# name                 s element_type       isthick parent_name shift_x shift_y rot_s_rad
# seq$start            0 Marker               False        None       0       0         0
# b1_entry             0 Marker               False        None       0       0         0
# b1..entry_map        0 ThinSliceBendEntry   False          b1    -0.1       0       0.8
# drift_b1..0          0 DriftSliceBend        True          b1       0       0         0
# b1..0         0.166667 ThinSliceBend        False          b1    -0.1       0       0.8
# drift_b1..1   0.166667 DriftSliceBend        True          b1       0       0         0
# b1..1         0.833333 ThinSliceBend        False          b1    -0.1       0       0.8
# drift_b1..2   0.833333 DriftSliceBend        True          b1       0       0         0
# b1..exit_map         1 ThinSliceBendExit    False          b1    -0.1       0       0.8
# b1_exit              1 Marker               False        None       0       0         0
# drift_0              1 Drift                 True        None       0       0         0
# b2_entry             2 Marker               False        None       0       0         0
# b2..entry_map        2 ThinSliceBendEntry   False          b2   0.002       0       0.3
# drift_b2..0          2 DriftSliceBend        True          b2       0       0         0
# b2..0          2.16667 ThinSliceBend        False          b2   0.002       0       0.3
# drift_b2..1    2.16667 DriftSliceBend        True          b2       0       0         0
# b2..1          2.83333 ThinSliceBend        False          b2   0.002       0       0.3
# drift_b2..2    2.83333 DriftSliceBend        True          b2       0       0         0
# b2..exit_map         3 ThinSliceBendExit    False          b2   0.002       0       0.3
# b2_exit              3 Marker               False        None       0       0         0
# drift_1              3 Drift                 True        None       0       0         0
# seq$end              4 Marker               False        None       0       0         0
# _end_point           4                      False        None       0       0         0

# Complete source: xtrack/examples/element_transformations/001_sliced_element_transform.py

Add multipolar components to elements

Multipolar components can be added to thick beam elements, as illustrated in the following example:

import xtrack as xt
from cpymad.madx import Madx

# Load a very simple sequence from MAD-X
mad = Madx()
mad.input("""
    seq: sequence, l=4;
    b1: sbend, at=0.5, angle=0.2, l=1;
    q1: quadrupole, at=2.5, k1=0.1, l=1;
    endsequence;

    beam;
    use,sequence=seq;
""")

line = xt.Line.from_madx_sequence(mad.sequence.seq)
line.build_tracker()

print('The line as imported from MAD-X:')
line.get_table().show()

# Add multipolar components to elements
line['b1'].knl[2] = 0.001 # Normal sxtupole component
line['q1'].ksl[3] = 0.002 # Skew octupole component

tt = line.get_table(attr=True)
tt.cols['s', 'element_type', 'isthick', 'k2l', 'k3sl'].show()
# returns:
#
# name       s element_type isthick   k2l  k3sl
# seq$start  0 Marker         False     0     0
# b1         0 Bend            True 0.001     0
# drift_0    1 Drift           True     0     0
# q1         2 Quadrupole      True     0 0.002
# drift_1    3 Drift           True     0     0
# seq$end    4 Marker         False     0     0
# _end_point 4                False     0     0

# Complete source: xtrack/examples/element_transformations/000a_multipolar_components.py

Multipolar components are propagated when the elements are sliced and can be updated also after the slicing by acting on the parent element. This is illustrated in the following example:

import xtrack as xt
from cpymad.madx import Madx

# Load a very simple sequence from MAD-X
mad = Madx()
mad.input("""
    seq: sequence, l=4;
    b1: sbend, at=0.5, angle=0.2, l=1;
    q1: quadrupole, at=2.5, k1=0.1, l=1;
    endsequence;

    beam;
    use,sequence=seq;
""")

line = xt.Line.from_madx_sequence(mad.sequence.seq)
line.build_tracker()

print('The line as imported from MAD-X:')
print(line.get_table())

# Add multipolar components to elements
line['b1'].knl[2] = 0.001 # Normal sxtupole component
line['q1'].ksl[3] = 0.002 # Skew octupole component

tt = line.get_table(attr=True)
tt.cols['s', 'element_type', 'isthick', 'k2l', 'k3sl'].show()
# prints:
#
# name       s element_type isthick   k2l  k3sl
# seq$start  0 Marker         False     0     0
# b1         0 Bend            True 0.001     0
# drift_0    1 Drift           True     0     0
# q1         2 Quadrupole      True     0 0.002
# drift_1    3 Drift           True     0     0
# seq$end    4 Marker         False     0     0
# _end_point 4                False     0     0

# Slice the line
slicing_strategies = [
    xt.Strategy(slicing=None),  # Default catch-all
    xt.Strategy(slicing=xt.Teapot(2), element_type=xt.Bend),
    xt.Strategy(slicing=xt.Teapot(2), element_type=xt.Quadrupole),
]
line.slice_thick_elements(slicing_strategies)
line.build_tracker()

# Inspect
tt = line.get_table(attr=True)
tt.cols['s', 'element_type', 'isthick', 'parent_name', 'k2l', 'k3sl']

# returns:
#
# Table: 21 rows, 7 cols
# name                 s element_type         isthick parent_name    k2l  k3sl
# seq$start            0 Marker                 False        None      0     0
# b1_entry             0 Marker                 False        None      0     0
# b1..entry_map        0 ThinSliceBendEntry     False          b1      0     0
# drift_b1..0          0 DriftSliceBend          True          b1      0     0
# b1..0         0.166667 ThinSliceBend          False          b1 0.0005     0
# drift_b1..1   0.166667 DriftSliceBend          True          b1      0     0
# b1..1         0.833333 ThinSliceBend          False          b1 0.0005     0
# drift_b1..2   0.833333 DriftSliceBend          True          b1      0     0
# b1..exit_map         1 ThinSliceBendExit      False          b1      0     0
# b1_exit              1 Marker                 False        None      0     0
# drift_0              1 Drift                   True        None      0     0
# q1_entry             2 Marker                 False        None      0     0
# drift_q1..0          2 DriftSliceQuadrupole    True          q1      0     0
# q1..0          2.16667 ThinSliceQuadrupole    False          q1      0 0.001
# drift_q1..1    2.16667 DriftSliceQuadrupole    True          q1      0     0
# q1..1          2.83333 ThinSliceQuadrupole    False          q1      0 0.001
# drift_q1..2    2.83333 DriftSliceQuadrupole    True          q1      0     0
# q1_exit              3 Marker                 False        None      0     0
# drift_1              3 Drift                   True        None      0     0
# seq$end              4 Marker                 False        None      0     0

# Update misalignment for one element. We act on the parent and the effect is
# propagated to the slices.
line['q1'].knl[2] = -0.003
line['q1'].ksl[3] = -0.004

# Inspect
tt = line.get_table(attr=True)
tt.cols['s', 'element_type', 'isthick', 'parent_name', 'k2l', 'k3sl']
# returns:
#
# Table: 21 rows, 7 cols
# name                 s element_type         isthick parent_name     k2l   k3sl
# seq$start            0 Marker                 False        None       0      0
# b1_entry             0 Marker                 False        None       0      0
# b1..entry_map        0 ThinSliceBendEntry     False          b1       0      0
# drift_b1..0          0 DriftSliceBend          True          b1       0      0
# b1..0         0.166667 ThinSliceBend          False          b1  0.0005      0
# drift_b1..1   0.166667 DriftSliceBend          True          b1       0      0
# b1..1         0.833333 ThinSliceBend          False          b1  0.0005      0
# drift_b1..2   0.833333 DriftSliceBend          True          b1       0      0
# b1..exit_map         1 ThinSliceBendExit      False          b1       0      0
# b1_exit              1 Marker                 False        None       0      0
# drift_0              1 Drift                   True        None       0      0
# q1_entry             2 Marker                 False        None       0      0
# drift_q1..0          2 DriftSliceQuadrupole    True          q1       0      0
# q1..0          2.16667 ThinSliceQuadrupole    False          q1 -0.0015 -0.002
# drift_q1..1    2.16667 DriftSliceQuadrupole    True          q1       0      0
# q1..1          2.83333 ThinSliceQuadrupole    False          q1 -0.0015 -0.002
# drift_q1..2    2.83333 DriftSliceQuadrupole    True          q1       0      0
# q1_exit              3 Marker                 False        None       0      0
# drift_1              3 Drift                   True        None       0      0
# seq$end              4 Marker                 False        None       0      0
# _end_point           4                        False        None       0      0

# Complete source: xtrack/examples/element_transformations/001a_sliced_multipolar_components.py

Cut line elements at given s positions

The method xtrack.Line.cut_at_s() allows for cutting the line elements at the specified s positions. In the example before we take the same toy ring introduced in the earlier example and we cut it into 100 equal length slices:

hundred_cuts = np.linspace(0, line.get_length(), num=100)
print(f'Make cuts every {hundred_cuts[1]} metres')  # => 21 centimetres
line.cut_at_s(s=list(hundred_cuts))

line.get_table()
# returns:
#
# Table: 139 rows, 7 cols
# name                    s element_type         isthick isreplica parent_name ...
# mqf.1_entry             0 Marker                 False     False        None
# mqf.1..0                0 ThickSliceQuadrupole    True     False       mqf.1
# mqf.1..1         0.214141 ThickSliceQuadrupole    True     False       mqf.1
# mqf.1_exit            0.3 Marker                 False     False        None
# d1.1..0               0.3 DriftSlice              True     False        d1.1
# d1.1..1          0.428283 DriftSlice              True     False        d1.1
# d1.1..2          0.642424 DriftSlice              True     False        d1.1
# d1.1..3          0.856566 DriftSlice              True     False        d1.1
# d1.1..4           1.07071 DriftSlice              True     False        d1.1
# d1.1..5           1.28485 DriftSlice              True     False        d1.1
# mb1.1_entry           1.3 Marker                 False     False        None
# mb1.1..entry_map      1.3 ThinSliceBendEntry     False     False       mb1.1
# mb1.1..0              1.3 ThickSliceBend          True     False       mb1.1
# mb1.1..1          1.49899 ThickSliceBend          True     False       mb1.1
# mb1.1..2          1.71313 ThickSliceBend          True     False       mb1.1
# mb1.1..3          1.92727 ThickSliceBend          True     False       mb1.1
# mb1.1..4          2.14141 ThickSliceBend          True     False       mb1.1
# etc...

# Complete source: xtrack/examples/toy_ring/007_cut_at_s.py