Xsuite environment
Overview
An xt.Environment is the shared container that keeps together:
Variables in
env.vars: scalar knobs and deferred expressions used to drive elements and lines. Their current value can be retrieved withenv['name'].Elements in
env.elements: magnets, markers, RF cavities, etc. They can be reused across multiple lines.Lines in
env.lines: ordered sequences of elements assembled from the environment.Additional data including reference particles and user-defined functions.
Accessing env['name'] returns the value of a variable, an element object,
or a line. Accessing env.ref['name'] returns a reference object that keeps
track of expressions; use references whenever you want to build expressions or
inspect dependencies.
The contents of all these containers can be inspected with the .get_table()
method, and a list of all names in each container is available with the .keys()
method.
A simple example illustrating the creation of an environment with variables, elements, and lines is shown below:
import xtrack as xt
# Create an environment
env = xt.Environment()
# Define variables
env['t_turn_s'] = 0.0
env['l_q'] = 1.0
env['kq'] = 0.12
env['kq.trim'] = '0.05 * kq' # deferred expression (is updated when kq changes)
env['kq.total'] = 'kq + kq.trim' # deferred expression
# Create elements reusing the variables
env.new('qf', xt.Quadrupole, length='l_q', k1='kq.total')
env.new('qd', xt.Quadrupole, length='l_q', k1='-kq.total')
env.new('dr', xt.Drift, length=6.0)
# Define two lines from the same elements
env.new_line(name='fodo', components=['qf', 'dr', 'qd', 'dr'])
env.new_line(name='half_fodo', components=['qf', 'dr'])
# Inspect containers
env.vars.get_table()
# VarsTable: 5 rows, 3 cols
# name value expr
# t_turn_s 0 None
# l_q 1 None
# kq 0.12 None
# kq.trim 0.006 (0.05 * kq)
# kq.total 0.126 (kq + kq.trim)
env.elements.get_table()
# Table: 3 rows, 7 cols
# name element_type isthick isreplica parent_name iscollective ...
# dr Drift True False None False
# qd Quadrupole True False None False
# qf Quadrupole True False None False
env.lines.get_table()
# Table: 2 rows, 3 cols
# name num_elements mode
# fodo 4 normal
# half_fodo 2 normal
# Quick name listings
env.vars.keys() # is: ['t_turn_s', 'l_q', 'kq', 'kq.trim', 'kq.total']
env.elements.keys() # is: ['dr', 'qd', 'qf']
env.lines.keys() # is: ['fodo', 'half_fodo']
# Inspect a deferred expression with references
env.ref['qf'].k1 # is a ref object
env.ref['qf'].k1.xdeps # gives basic information on the ref.
# It prints:
# Ref(element_refs['qf'].k1, expr=vars['kq.total'], value=0.126)
# Additional information can be obtained from the `info()` method
env.ref['qf'].k1.xdeps.info()
# prints:
# Info for element_refs['qf'].k1
#
# value: 0.126
#
# controlled by expr:
# element_refs['qf'].k1 = vars['kq.total']
#
# expr_dependencies:
# vars['kq.total'] = 0.126
#
# controlled_targets: None
env.ref['kq.total'].xdeps.info()
# Info for vars['kq.total']
#
# value: 0.126
#
# controlled by expr:
# vars['kq.total'] = (vars['kq'] + vars['kq.trim'])
#
# expr_dependencies:
# vars['kq'] = 0.12
# vars['kq.trim'] = 0.006
#
# controlled_targets:
# element_refs['qd'].k1
# element_refs['qf'].k1
Variables
Defining variables and deferred expressions
Variables can be provided by direct item assignment. Strings are parsed as expressions and can mix Python, NumPy, and other variables in the environment.
import numpy as np
import xtrack as xt
env = xt.Environment()
env['l_q'] = 1.2
env['kq'] = 0.08
env['phi'] = np.pi / 8
env['kq.trim'] = '1.1 * kq' # deferred expression
env['kq.total'] = 'kq + kq.trim'
env['kq.total'] # -> 0.168
Expressions provided as strings stay deferred: changing an upstream variable updates all dependents automatically.
env['kq'] = 0.2
env['kq.trim'] # -> 0.22 (recomputed)
Inspecting variables
env.vars.get_table() summarizes current values and expressions:
env.vars.get_table()
# name value expr
# l_q 1.2 None
# kq 0.08 None
# kq.trim 0.088 (1.1 * kq)
# kq.total 0.168 (kq + kq.trim)
To understand dependencies, fetch the reference and ask for its xdeps.info:
env.ref['kq.total'].xdeps.info()
# prints:
# Info for vars['kq.total']
#
# value: 0.168
#
# controlled by expr:
# vars['kq.total'] = (vars['kq'] + vars['kq.trim'])
#
# expr_dependencies:
# vars['kq'] = 0.08
# vars['kq.trim'] = 0.08800000000000001
#
# controlled_targets: None
env.ref['kq'].xdeps.info()
# Info for vars['kq']
#
# value: 0.08
#
# Not controlled by other entities.
#
# controlled_targets:
# vars['kq.trim']
# vars['kq.total']
Renaming variables
Variables can renamed as follows
env.vars.rename('kq', 'kq.main') # updates expressions automatically
Elements
Creating elements
Elements can be added directly to env.elements or created with
env.new(...). The most direct way is to instantiate an element and store it:
import xtrack as xt
env = xt.Environment()
env['kq'] = 0.08
env['kq.trim'] = '1.1 * kq'
env['kq.total'] = 'kq + kq.trim'
env.elements['mq0'] = xt.Quadrupole(length=3.0, k1=0.1)
env['mq0'].k1 = '0.5 * kq.total' # attach a deferred expression
Elements can also be created using Environment.new. Deferred expressions can
be specified directly in the constructor:
env['l_q'] = 1.2
env['kq'] = 0.08
env['kq.total'] = '2 * kq'
env.new('mq1', xt.Quadrupole, length='l_q', k1='kq.total')
env.new('ms1', xt.Sextupole, length=0.3, k2='-0.5*kq.total')
env.new can also clone an existing element; the parent becomes the prototype
for the new one:
env.new('mq2', xt.Quadrupole, length='l_q', k1='kq.total')
env.new('mq2.d', 'mq2', k1='-kq.total') # clone 'mq2' and override k1
env['mq2.d'].length # -> expression 'l_q', inherited from 'mq2'
env['mq2.d'].k1 # -> expression '-kq.total' (override)
Inspecting deferred attributes
You can inspect deferred attributes the same way as variables:
env.ref['mq0'].k1.xdeps.info()
# prints:
# Info for element_refs['mq0'].k1
#
# value: 0.08
#
# controlled by expr:
# element_refs['mq0'].k1 = (0.5 * vars['kq.total'])
#
# expr_dependencies:
# vars['kq.total'] = 0.16
#
# controlled_targets: None
Listing elements
To get a table with all the elements stored in the environment (including
attributes), use env.elements.get_table(attr=True):
env = xt.Environment()
env['l_q'] = 1.2
env['kq'] = 0.08
env['kq.total'] = '2 * kq'
env.elements['mq0'] = xt.Quadrupole(length=3.0, k1=0.1)
env.elements['dr0'] = xt.Drift(length=0.4)
env.new('mq1', xt.Quadrupole, length='l_q', k1='kq.total')
env.new('ms1', xt.Sextupole, length=0.3, k2='-0.5*kq.total')
env.new('dr1', xt.Drift, length=0.2)
env.new('mq2', xt.Quadrupole, length='l_q', k1='kq.total')
env.new('mq2.d', 'mq2', k1='-kq.total')
tt = env.elements.get_table(attr=True)
tt.cols['name element_type length k1l']
# Table: 7 rows, 4 cols
# name element_type length k1l
# dr0 Drift 0.4 0
# dr1 Drift 0.2 0
# mq0 Quadrupole 3 0.3
# mq1 Quadrupole 1.2 0.192
# mq2 Quadrupole 1.2 0.192
# mq2.d Quadrupole 1.2 -0.192
# ms1 Sextupole 0.3 0
Tables support convenient filtering and regex matching:
tt.rows['mq.*']
# Table: 4 rows, 124 cols
# name element_type isthick isreplica parent_name ...
# mq0 Quadrupole True False None
# mq1 Quadrupole True False None
# mq2 Quadrupole True False None
# mq2.d Quadrupole True False None
tt.rows.match(element_type='Dr.*|Sext.*')
# Table: 3 rows, 124 cols
# name element_type isthick isreplica parent_name ...
# dr0 Drift True False None
# dr1 Drift True False None
# ms1 Sextupole True False None
tt.rows.match_not(element_type='Dr.*')
# Table: 5 rows, 124 cols
# name element_type isthick isreplica parent_name ...
# mq0 Quadrupole True False None
# mq1 Quadrupole True False None
# mq2 Quadrupole True False None
# mq2.d Quadrupole True False None
# ms1 Sextupole True False None
Setting element properties
env.set assigns numbers or expressions to one or many elements. When a
string is passed, it is treated as a deferred expression.
env.set('mq2', k1='1.05 * kq.total')
env.set(['mq2', 'mq2.d'], k1='0.5 * kq.total') # broadcast over a list
You can also target groups via table filters:
tt = env.elements.get_table(attr=True)
tt_quad = tt.rows.match(element_type='Quadrupole')
tt_quad
# Table: 4 rows, 124 cols
# name element_type isthick isreplica parent_name ...
# mq0 Quadrupole True False None
# mq1 Quadrupole True False None
# mq2 Quadrupole True False None
# mq2.d Quadrupole True False None
env.set(tt_quad, integrator='yoshida4') # applies to all quads above
Remove elements
Elements can be deleted from the environment when they are not used in any line:
del env.elements['mq2.d'] # or env.elements.remove('mq2.d')
Lines
Create line as list of existing elements
The simplest way to create a line is to define it as a sequence of existing elements:
env = xt.Environment()
env.new('qf', xt.Quadrupole, length=1.0, k1=0.1)
env.new('qd', xt.Quadrupole, length=1.0, k1=-0.1)
env.new('dr', xt.Drift, length=5.0)
myline = env.new_line(components=['qf', 'dr', 'qd', 'dr'])
In this case, the line is created but not stored in env.lines. Storing it
ensures it is saved with the environment:
env.lines['fodo'] = myline
Alternatively, pass a name to store it automatically:
env.new_line(name='fodo', components=['qf', 'dr', 'qd', 'dr'])
Once stored, it is accessible as env['fodo'] or env.fodo.
Line inspection
You can inspect a line using line.get_table():
env['fodo'].get_table()
# Table: 5 rows, 11 cols
# name s element_type isthick isreplica ...
# qf 0 Quadrupole True False
# dr::0 1 Drift True False
# qd 6 Quadrupole True False
# dr::1 7 Drift True False
# _end_point 12 False False
Create line by placing elements
Lines can also be defined by specifying element positions, either absolute or relative to other elements:
env = xt.Environment()
env['s.q1'] = 3.0
env['s.q2'] = 5.0
env['ds.q4'] = 5.0
env['kquad'] = 0.1
env['line_length'] = 12.0
env.new('q1', xt.Quadrupole, length=1.0, k1='kquad')
env.new('q2', xt.Quadrupole, length=1.0, k1='-kquad')
env.new('q3', xt.Quadrupole, length=1.0, k1='kquad')
env.new('q4', xt.Quadrupole, length=1.0, k1='-kquad')
env.new('s4', xt.Sextupole, length=0.1)
myline = env.new_line(name='myline', length='line_length', components=[
env.place('q1', at='s.q1'), # center at s=3.0
env.place('q2', anchor='start', at=5.0), # start at s=5.0
env.place('q3', anchor='start', at='q2@end'), # start at end of q2
env.place('q4', anchor='center', at='ds.q4',
from_='q3@start'), # center 5 m from q3 start
env.place('s4'), # right after previous
])
tt = myline.get_table()
tt.show(cols=['s_start', 's_center', 's_end'])
# name s_start s_center s_end
# ||drift_1 0 1.25 2.5
# q1 2.5 3 3.5
# ||drift_2 3.5 4.25 5
# q2 5 5.5 6
# q3 6 6.5 7
# ||drift_3 7 8.75 10.5
# q4 10.5 11 11.5
# s4 11.5 11.55 11.6
# ||drift_4 11.6 11.8 12
# _end_point 12 12 12
Elements can also be created and placed with a single instruction:
myline2 = env.new_line(name='myline2', length='line_length', components=[
env.new('q10', xt.Quadrupole, length=1.0, k1='kquad', at='s.q1'),
env.new('q20', xt.Quadrupole, length=1.0, k1='-kquad',
anchor='start', at=5.0),
env.new('q30', xt.Quadrupole, length=1.0, k1='kquad',
anchor='start', at='q20@end'),
env.new('q40', xt.Quadrupole, length=1.0, k1='-kquad',
anchor='center', at='ds.q4', from_='q30@start'),
env.new('s40', xt.Sextupole, length=0.1), # placed after previous
])
tt = myline2.get_table()
tt.show(cols=['s_start', 's_center', 's_end'])
# name s_start s_center s_end
# ||drift_4 0 1.25 2.5
# q10 2.5 3 3.5
# ||drift_5 3.5 4.25 5
# q20 5 5.5 6
# q30 6 6.5 7
# ||drift_6 7 8.75 10.5
# q40 10.5 11 11.5
# s40 11.5 11.55 11.6
# ||drift_7 11.6 11.8 12
# _end_point 12 12 12
“compose” mode
When the line contains many element, it is inconvenient to have to specify all components in a single Python statement. In this case, it is possible to use the “compose” mode, where each component is added by a separate instruction, as illustrated in the following example:
env = xt.Environment()
env['s.q1'] = 3.0
env['s.q2'] = 5.0
env['ds.q4'] = 5.0
env['kquad'] = 0.1
env['line_length'] = 12.0
env.new('q1', xt.Quadrupole, length=1.0, k1='kquad')
env.new('q2', xt.Quadrupole, length=1.0, k1='-kquad')
env.new('q3', xt.Quadrupole, length=1.0, k1='kquad')
env.new('q4', xt.Quadrupole, length=1.0, k1='-kquad')
env.new('s4', xt.Sextupole, length=0.1)
myline = env.new_line(name='myline', length='line_length', compose=True)
myline.place('q1', at='s.q1') # center at s=3.0
myline.place('q2', anchor='start', at=5.0) # start at s=5.0
myline.place('q3', anchor='start', at='q2@end') # start at end of q2
myline.place('q4', anchor='center', at='ds.q4', from_='q3@start') # center 5 m from q3 start
myline.place('s4') # right after previous
myline.end_compose()
tt = myline.get_table()
tt.show(cols=['s_start', 's_center', 's_end'])
# name s_start s_center s_end
# ||drift_1 0 1.25 2.5
# q1 2.5 3 3.5
# ||drift_2 3.5 4.25 5
# q2 5 5.5 6
# q3 6 6.5 7
# ||drift_3 7 8.75 10.5
# q4 10.5 11 11.5
# s4 11.5 11.55 11.6
# ||drift_4 11.6 11.8 12
# _end_point 12 12 12
Also in compose mode, elements can be created inline while placing them:
myline2 = env.new_line(name='myline2', length='line_length', compose=True)
myline2.new('q10', xt.Quadrupole, length=1.0, k1='kquad', at='s.q1'),
myline2.new('q20', xt.Quadrupole, length=1.0, k1='-kquad', anchor='start', at=5.0),
myline2.new('q30', xt.Quadrupole, length=1.0, k1='kquad', anchor='start', at='q20@end'),
myline2.new('q40', xt.Quadrupole, length=1.0, k1='-kquad',
anchor='center', at='ds.q4', from_='q30@start'),
myline2.new('s40', xt.Sextupole, length=0.1), # placed after previous
myline2.end_compose()
tt = myline2.get_table()
tt.show(cols=['s_start', 's_center', 's_end'])
# name s_start s_center s_end
# ||drift_4 0 1.25 2.5
# q10 2.5 3 3.5
# ||drift_5 3.5 4.25 5
# q20 5 5.5 6
# q30 6 6.5 7
# ||drift_6 7 8.75 10.5
# q40 10.5 11 11.5
# s40 11.5 11.55 11.6
# ||drift_7 11.6 11.8 12
# _end_point 12 12 12
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, 11 cols
# name s element_type isthick isreplica parent_name ...
# ||drift_3 0 Drift True False None
# bb 1.5 Bend True False None
# ||drift_4 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_2::1 5.175 Drift True False None
# mq2_subline::0 5.525 Quadrupole True False None
# ||drift_5 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::2 7.825 Drift True False None
# mid_subline::1 8.175 Marker False False None
# ||drift_2::3 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
Line mirroring and composition
Lines can be mirrored with the unary minus operator and combined with addition and multiplication:
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.0
env['l_cell'] = 20.0
env['n_bends'] = 24
env['angle_bend'] = 'pi / n_bends'
env.new('mq', xt.Quadrupole, length='l_quad')
env.new('mb', xt.Bend, length='l_bend', angle='angle_bend')
env.new('mqf', 'mq', k1=0.1)
env.new('mqd', 'mq', k1=-0.1)
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'),
])
mirror_arc_half_cell = -arc_half_cell
mirror_arc_half_cell.get_table()
# Table: 8 rows, 11 cols
# 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
arc_cell = -arc_half_cell + arc_half_cell # mirror then concatenate
arc_cell.get_table()
# Table: 15 rows, 11 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
arc = 2 * arc_cell
arc.get_table()
# Table: 29 rows, 11 cols
# name s element_type isthick isreplica parent_name ...
# mqd.l::0 0 Quadrupole True True mqd
# ||drift_3::0 1 Drift True False None
# mb2.l::0 1.3 Bend True True mb2
# ||drift_2::0 4.8 Drift True False None
# mb1.l::0 5.2 Bend True True mb1
# ||drift_1::0 8.7 Drift True False None
# mqf.l::0 9.5 Quadrupole True True mqf
# mqf.r::0 10.5 Quadrupole True True mqf
# ||drift_1::1 11.5 Drift True False None
# mb1.r::0 12.3 Bend True True mb1
# ||drift_2::1 15.8 Drift True False None
# mb2.r::0 16.2 Bend True True mb2
# ||drift_3::1 19.7 Drift True False None
# mqd.r::0 20 Quadrupole True True mqd
# mqd.l::1 21 Quadrupole True True mqd
# ||drift_3::2 22 Drift True False None
# mb2.l::1 22.3 Bend True True mb2
# ||drift_2::2 25.8 Drift True False None
# mb1.l::1 26.2 Bend True True mb1
# ||drift_1::2 29.7 Drift True False None
# mqf.l::1 30.5 Quadrupole True True mqf
# mqf.r::1 31.5 Quadrupole True True mqf
# ||drift_1::3 32.5 Drift True False None
# mb1.r::1 33.3 Bend True True mb1
# ||drift_2::3 36.8 Drift True False None
# mb2.r::1 37.2 Bend True True mb2
# ||drift_3::3 40.7 Drift True False None
# mqd.r::1 41 Quadrupole True True mqd
# _end_point 42 False False None
Insert elements
It is possible to insert elements in a line also after its creation. The position of the new elements can be specified as absolute s position or as relative to an existing element. This is illustrated in the following example:
import xtrack as xt
# Create an environment
env = xt.Environment()
# Create a line with two quadrupoles and a marker
line = env.new_line(name='myline', components=[
env.new('q0', xt.Quadrupole, length=2.0, at=10.),
env.new('q1', xt.Quadrupole, length=2.0, at=20.),
env.new('m0', xt.Marker, at=40.),
])
tt0 = line.get_table()
tt0.show(cols=['s_start', 's_center', 's_end'])
# is:
# name s_start s_center s_end
# drift_1 0 4.5 9
# q0 9 10 11
# drift_2 11 15 19
# q1 19 20 21
# drift_3 21 30.5 40
# m0 40 40 40
# _end_point 40 40 40
# Create a set of new elements to be placed
env.new('s1', xt.Sextupole, length=0.1, k2=0.2)
env.new('s2', xt.Sextupole, length=0.1, k2=-0.2)
env.new('m1', xt.Marker)
env.new('m2', xt.Marker)
env.new('m3', xt.Marker)
# Insert the new elements in the line
line.insert([
env.place('s1', at=5.),
env.place('s2', anchor='end', at=-5., from_='q1@start'),
env.place(['m1', 'm2'], at='m0@start'),
env.place('m3', at='m0@end'),
])
tt = line.get_table()
tt.show(cols=['s_start', 's_center', 's_end'])
# is:
# name s_start s_center s_end
# ||drift_4 0 2.475 4.95
# s1 4.95 5 5.05
# ||drift_6 5.05 7.025 9
# q0 9 10 11
# ||drift_7 11 12.45 13.9
# s2 13.9 13.95 14
# ||drift_8 14 16.5 19
# q1 19 20 21
# ||drift_3 21 30.5 40
# m1 40 40 40
# m2 40 40 40
# m0 40 40 40
# m3 40 40 40
# _end_point 40 40 40
# Complete source: xtrack/examples/lattice_design/007b_insert_elements.py
Insert custom elements and elements instantiated by the user
It is possible to insert elements that are created by the user using the class directly instead of using the Environment.new method. This can be done in a single step or alternatively by first adding the element to the environment and then inserting it in the line. This is illustrated in the following example:
import xtrack as xt
# Create an environment
env = xt.Environment()
# Create a line with two quadrupoles and a marker
line = env.new_line(name='myline', components=[
env.new('q0', xt.Quadrupole, length=2.0, at=10.),
env.new('q1', xt.Quadrupole, length=2.0, at=20.),
env.new('m0', xt.Marker, at=40.),
])
tt0 = line.get_table()
tt0.show(cols=['s_start', 's_center', 's_end'])
# is:
# name s_start s_center s_end
# drift_1 0 4.5 9
# q0 9 10 11
# drift_2 11 15 19
# q1 19 20 21
# drift_3 21 30.5 40
# m0 40 40 40
# _end_point 40 40 40
# Instantiate elements using the class directly
mysext = xt.Sextupole(length=0.1, k2=0.2)
myaperture = xt.LimitEllipse(a=0.01, b=0.02)
# Insert the element in the line and, contextually, define its name:
line.insert('s1', mysext, at=5., from_='q1')
# Alternatively, add the element to the environment and then do the insertion:
env.elements['ap1'] = myaperture
line.insert('ap1', at='q0@start')
tt = line.get_table()
tt.show(cols=['s_start', 's_center', 's_end'])
# is:
# name s_start s_center s_end
# drift_1 0 4.5 9
# ap1 9 9 9
# q0 9 10 11
# drift_2 11 15 19
# q1 19 20 21
# drift_3..0 21 22.975 24.95
# s1 24.95 25 25.05
# drift_3..2 25.05 32.525 40
# m0 40 40 40
# _end_point 40 40 40
# The above is convenient when adding elements of types defined by the user.
# For example:
class MyElement:
def __init__(self, param=1.0):
self.param = param
def track(self, particles):
# Custom tracking logic
self.px += self.param * particles.x
# Create multiple instances of MyElement and store them in the environment
env.elements['my_element1'] = MyElement(param=2.0)
env.elements['my_element2'] = MyElement(param=3.0)
env.elements['my_element3'] = MyElement(param=4.0)
# Insert in the lines
line.insert([
env.place('my_element1', at=12.),
env.place('my_element2', at=1, from_='my_element1'),
env.place('my_element3', at=2, from_='my_element2')
]
)
tt = line.get_table()
tt.show(cols=['s_start', 's_center', 's_end'])
# Complete source: xtrack/examples/lattice_design/007h_insert_element_instantiated_by_user.py
Insert a line into another line
It is also possible to insert entire lines, as illustrated in the following example:
import xtrack as xt
# Create an environment
env = xt.Environment()
# Create a line with two quadrupoles and a marker
line = env.new_line(name='myline', components=[
env.new('q0', xt.Quadrupole, length=2.0, at=10.),
env.new('q1', xt.Quadrupole, length=2.0, at=20.),
env.new('m0', xt.Marker, at=50),
])
tt0 = line.get_table()
tt0.show(cols=['s_start', 's_center', 's_end'])
# is:
# drift_1 0 4.5 9
# q0 9 10 11
# drift_2 11 15 19
# q1 19 20 21
# drift_3 21 35.5 50
# m0 50 50 50
# Create a set of new elements to be placed
env.new('s1', xt.Sextupole, length=2., k2=0.2)
env.new('s2', xt.Sextupole, length=2., k2=-0.2)
env.new('m1', xt.Marker)
env.new('m2', xt.Marker)
env.new('m3', xt.Marker)
subline = env.new_line(components=[
env.place('s1', at=1.0),
env.place('s2', at=5.0),
env.place('m1', at='s1@start'),
env.place('m2', at='s2@start')])
# Insert the new elements in the line
line.insert([
env.place(subline, anchor='start', at=1., from_='q1@end'),
env.place('m3', at=30.)])
tt = line.get_table()
tt.show(cols=['s_start', 's_center', 's_end'])
# is
# name s_start s_center s_end
# drift_1 0 4.5 9
# q0 9 10 11
# drift_2 11 15 19
# q1 19 20 21
# drift_3..0 21 21.5 22
# m1 22 22 22
# s1 22 23 24
# drift_4 24 25 26
# m2 26 26 26
# s2 26 27 28
# drift_3..4 28 29 30
# m3 30 30 30
# drift_3..5 30 40 50
# m0 50 50 50
# _end_point 50 50 50
# Complete source: xtrack/examples/lattice_design/007d_insert_line.py
Simplified syntax for single insertion
A compact syntax is available to perform a single insertion in a line. Note that when multiple insertions need to be made, it is significantly faster to install all the elements at once, as shown in the previous example. The compact syntax for single insertion is illustrated in the following example:
import xtrack as xt
# Create an environment
env = xt.Environment()
# Create a line with two quadrupoles and a marker
line = env.new_line(name='myline', components=[
env.new('q0', xt.Quadrupole, length=2.0, at=10.),
env.new('q1', xt.Quadrupole, length=2.0, at=20.),
env.new('m0', xt.Marker, at=40.),
])
tt0 = line.get_table()
tt0.show(cols=['s_start', 's_center', 's_end'])
# is:
# name s_start s_center s_end
# drift_1 0 4.5 9
# q0 9 10 11
# drift_2 11 15 19
# q1 19 20 21
# drift_3 21 30.5 40
# m0 40 40 40
# _end_point 40 40 40
# Create a set of new elements to be placed
env.new('s1', xt.Sextupole, length=0.1, k2=0.2)
env.new('s2', xt.Sextupole, length=0.1, k2=-0.2)
env.new('m1', xt.Marker)
env.new('m2', xt.Marker)
env.new('m3', xt.Marker)
# Insert the new elements in the line
line.insert('s1', at=5.)
line.insert('s2', anchor='end', at=-5., from_='q1@start')
line.insert(['m1', 'm2'], at='m0@start')
line.insert('m3', at='m0@end')
tt = line.get_table()
tt.show(cols=['s_start', 's_center', 's_end'])
# is:
# name s_start s_center s_end
# drift_1..0 0 2.475 4.95
# s1 4.95 5 5.05
# drift_1..2 5.05 7.025 9
# q0 9 10 11
# drift_2..0 11 12.45 13.9
# s2 13.9 13.95 14
# drift_2..2 14 16.5 19
# q1 19 20 21
# drift_3 21 30.5 40
# m1 40 40 40
# m0 40 40 40
# m3 40 40 40
# Complete source: xtrack/examples/lattice_design/007c_insert_individual.py
Append elements to a line
New elements can also be installed at the end of a line, as illustrated in the following example:
import xtrack as xt
# Create an environment
env = xt.Environment()
# Create a line with two quadrupoles and a marker
line = env.new_line(name='myline', components=[
env.new('q0', xt.Quadrupole, length=2.0, at=10.),
env.new('q1', xt.Quadrupole, length=2.0, at=20.),
env.new('m0', xt.Marker, at=40.),
])
tt0 = line.get_table()
tt0.show(cols=['s_start', 's_center', 's_end'])
# is:
# name s_start s_center s_end
# drift_1 0 4.5 9
# q0 9 10 11
# drift_2 11 15 19
# q1 19 20 21
# drift_3 21 30.5 40
# m0 40 40 40
# _end_point 40 40 40
# Create a set of new elements to be placed
env.new('s1', xt.Sextupole, length=0.1, k2=0.2)
env.new('s2', xt.Sextupole, length=0.1, k2=-0.2)
env.new('m1', xt.Marker)
env.new('m2', xt.Marker)
env.new('m3', xt.Marker)
# Insert the new elements in the line
line.append(['m1', 's1', 'm2', 's2', 'm3'])
tt = line.get_table()
tt.show(cols=['s_start', 's_center', 's_end'])
# is:
# name s_start s_center s_end
# drift_1 0 4.5 9
# q0 9 10 11
# drift_2 11 15 19
# q1 19 20 21
# drift_3 21 30.5 40
# m0 40 40 40
# m1 40 40 40
# s1 40 40.05 40.1
# m2 40.1 40.1 40.1
# s2 40.1 40.15 40.2
# m3 40.2 40.2 40.2
# _end_point 40.2 40.2 40.2
# Elements can be appended also when they are created using the class directly.
# The element name is defined contextually:
myoct = xt.Octupole(length=0.1, k3=0.3)
line.append('o1', myoct)
tt = line.get_table()
tt.show(cols=['s_start', 's_center', 's_end'])
# is:
# name s_start s_center s_end
# drift_1 0 4.5 9
# q0 9 10 11
# drift_2 11 15 19
# q1 19 20 21
# drift_3 21 30.5 40
# m0 40 40 40
# m1 40 40 40
# s1 40 40.05 40.1
# m2 40.1 40.1 40.1
# s2 40.1 40.15 40.2
# m3 40.2 40.2 40.2
# o1 40.2 40.25 40.3
# _end_point 40.3 40.3 40.3
# Complete source: xtrack/examples/lattice_design/007e_append_elements.py
Remove elements from a line
Elements can be removed from a line using the Line.remove method. Thick elements are replaced by drift spaces, so that the position of all other elements is preserved. This is illustrated in the following example:
import xtrack as xt
# Create an environment
env = xt.Environment()
# Create a line with two quadrupoles and a marker
line = env.new_line(name='myline', components=[
env.new('q0', xt.Quadrupole, length=2.0, at=10.),
env.new('q1', xt.Quadrupole, length=2.0, at=20.),
env.new('m0', xt.Marker, at=40.),
])
tt0 = line.get_table()
tt0.show(cols=['s_start', 's_center', 's_end'])
# is:
# name s_start s_center s_end
# drift_1 0 4.5 9
# q0 9 10 11
# drift_2 11 15 19
# q1 19 20 21
# drift_3 21 30.5 40
# m0 40 40 40
# _end_point 40 40 40
# Remove two elements
line.remove('q1')
line.remove('m0')
tt = line.get_table()
tt.show(cols=['s_start', 's_center', 's_end'])
# name s_start s_center s_end
# drift_1 0 4.5 9
# q0 9 10 11
# drift_2 11 15 19
# drift_4 19 20 21
# drift_3 21 30.5 40
# _end_point 40 40 40
# Complete source: xtrack/examples/lattice_design/007f_remove_elements.py
Replace elements in a line
Elements in a line can be replaced with elements having the same length, as illustrated in the following example:
import xtrack as xt
# Create an environment
env = xt.Environment()
# Create a line with two quadrupoles and a marker
line = env.new_line(name='myline', components=[
env.new('q0', xt.Quadrupole, length=2.0, at=10.),
env.new('q1', xt.Quadrupole, length=2.0, at=20.),
env.new('m0', xt.Marker, at=40.),
])
tt0 = line.get_table()
tt0.show(cols=['s_start', 's_center', 's_end'])
# is:
# name s_start s_center s_end
# drift_1 0 4.5 9
# q0 9 10 11
# drift_2 11 15 19
# q1 19 20 21
# drift_3 21 30.5 40
# m0 40 40 40
# _end_point 40 40 40
# Create a new element
env.new('s1', xt.Sextupole, length=2, k2=0.2)
# Replace `q1` with `s1`
line.replace('q1', 's1')
tt = line.get_table()
tt.show(cols=['s_start', 's_center', 's_end'])
# is:
# name s_start s_center s_end
# drift_1 0 4.5 9
# q0 9 10 11
# drift_2 11 15 19
# s1 19 20 21
# drift_3 21 30.5 40
# m0 40 40 40
# _end_point 40 40 40
# Complete source: xtrack/examples/lattice_design/007g_replace_elements.py
Slice elements in a line
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.set_particle_ref('proton', p0c=1.2e9)
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
])
# 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
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
Remove lines
Lines stored in the environment can be removed with del:
del env.lines['fodo']
Reference particles
Reference particles define the beam mass, energy, and charge used by optics
tools such as Line.twiss. A reference particle must be set before running
Twiss.
Set directly on a line
Assign a built-in species with the desired energy (total energy in eV):
import xtrack as xt
env = xt.Environment()
env['l_quad'] = 1.0
env['k_quad'] = 0.1
env.new('qf', xt.Quadrupole, length='l_quad', k1='k_quad')
env.new('qd', xt.Quadrupole, length='l_quad', k1='-k_quad')
env.new('dr', xt.Drift, length=5.0)
line = env.new_line(components=['qf', 'dr', 'qd', 'dr'])
line.set_particle_ref('proton', energy0=2e9) # 2 GeV total energy
# alternative set the momentum, kinetic energy, or gamma
# line.set_particle_ref('proton', p0c=2e9)
# line.set_particle_ref('proton', kinetic_energy0=200e6)
# line.set_particle_ref('proton', gamma0=10.5)
# set mass0, charge0 instead of particle type
# line.set_particle_ref(mass0=xt.PROTON_MASS_EV, charge0=1, p0c=2e9)
tw = line.twiss4d() # uses the reference particle above
Reuse particles stored in the environment
You can define reusable particles (optionally with deferred expressions) and attach them as the line reference. Particles can live in the environment and be driven by variables.
import xtrack as xt
env = xt.Environment()
env['energy_gev'] = 5.0
env.new_particle(name='my_ref_part',
mass0=xt.PROTON_MASS_EV,
q0=1,
energy0='energy_gev * 1e9') # deferred expression
# Inspect stored particles in the environment
env.particles.get_table()
# is:
# Table: 1 row, 7 cols
# name mass0 charge0 energy0 p0c ...
# my_ref_part 9.38272e+08 1 5.25e+09 5.16548e+09
# Any line built from this environment can reuse the same particle definition
line = env.new_line(name='ring', components=[
env.new('qf', xt.Quadrupole, length=1.0, k1=0.1),
env.new('dr', xt.Drift, length=5.0),
env.new('qd', xt.Quadrupole, length=1.0, k1=-0.1),
env.new('dr2', xt.Drift, length=5.0),
])
# Set reference particle for the line
line.particle_ref = 'my_ref_part'
env['energy_gev'] = 5.25 # updates reference energy automatically
tw = line.twiss4d() # Twiss now uses the updated reference
# Inspect the deferred expression on the particle
env.ref['my_ref_part'].energy0.xdeps.info()
# Info for particles['my_ref_part'].energy0
#
# value: [5.25e+09]
#
# controlled by expr:
# particles['my_ref_part'].energy0 = (vars['energy_gev'] * 1000000000.0)
#
# expr_dependencies:
# vars['energy_gev'] = 5.25
#
# controlled_targets: None
Link lattice properties to reference particle parameters
Lattice parameters can depend on reference particle quantities. For example, you can tie element strengths to the reference particle rigidity to obtain a normalized strength corresponding to a fixed magnetic field, independent of the reference momentum.
import xtrack as xt
env = xt.Environment()
env['p0c_optics_gev'] = 1.0 # momentum used for optics (GeV)
env.new_particle('particle/b1', mass0=xt.PROTON_MASS_EV,
q0=1, p0c='p0c_optics_gev * 1e9')
env['spectrometer_b_tesla'] = 3.0
env['l.spectrometer'] = 2.0
env.new('spectrometer.b1', 'Bend', angle=0, length='l.spectrometer',
k0=env.ref['spectrometer_b_tesla'] / env.ref['particle/b1'].rigidity0[0])
line = env.new_line(components=['spectrometer.b1'])
line.particle_ref = 'particle/b1'
env['spectrometer.b1'].k0 # -> ~0.899 for p0c_optics_gev = 1
line.particle_ref.p0c = 450e9 # change reference momentum
env['spectrometer.b1'].k0 # -> ~0.002; field stays 3 T
Remove reference particles
Particles stored in the environment can be removed with del:
# Remove a stored particle
del env.particles['my_ref_part']
Saving and loading environment or individual lines
Environments (including all elements and lines) can be serialized to JSON and loaded back, and individual lines can be saved and loaded separately.
import xtrack as xt
env = xt.Environment()
env['k1'] = 0.1
env.new('qf', xt.Quadrupole, length=1.0, k1='k1')
env.new('qd', xt.Quadrupole, length=1.0, k1='-k1')
env.new_line(name='line_a', components=['qf', 'qd'])
env.new_line(name='line_b', components=['qd', 'qf'])
# Save whole environment (variables, elements, lines)
env.to_json('env.json')
# Reload environment
env2 = xt.load('env.json')
# Save a single line (variables and elements are included automatically)
env['line_a'].to_json('line_a.json')
# Reload the line
line_loaded = xt.load('line_a.json')
env3 = line_loaded.env # get the environment from the line
Loading MAD-X lattices
Native MAD-X parser (recommended)
Xsuite can read MAD-X lattice files directly without launching MAD-X. Provide
one or more files (any mix of .madx, .seq, or .str) to
xt.load; the parser understands definitions such as variables, sequences,
and lines. Files containing MAD-X computations (twiss, survey,
match, etc.) are not supported and will raise an error.
import xtrack as xt
# The order of files matters if later files depend on earlier definitions
env = xt.load([
'ps_sftpro/ps.seq',
'ps_sftpro/ps_hs_sftpro.str',
])
# Access lines or variables defined in the MAD-X files
line = env.lines['ring']
kq = env['kqf1']
You can also pass MAD-X source code directly as a string by specifying the format explicitly:
mad_src = '''
! Define variables and elements up-front (deferred expressions are supported)
l_q := 1.0;
l_cell := 12.0;
kq := 0.12;
kq_f := kq;
kq_d := -kq;
qf: quadrupole, l:=l_q, k1:=kq_f;
qd: quadrupole, l:=l_q, k1:=kq_d;
fodo: sequence, l=l_cell;
qf, at=0.5 * l_q; ! center at s=0.5 * l_q
qd, at=l_cell/2 + 0.5 * l_q; ! placed using deferred expressions
endsequence;
'''
env = xt.load(string=mad_src, format='madx')
line = env['fodo']
This path is the long-term supported way to import MAD-X lattices into Xsuite.
Import via cpymad (legacy)
You can also import a MAD-X sequence using cpymad by first running MAD-X and
then converting the in-memory sequence to an Xsuite line. This route depends on
MAD-X being available and will be discontinued alongside MAD-X end-of-support
plans.
from cpymad.madx import Madx
import xtrack as xt
mad = Madx()
# example files from xtrack/test_data/ps_sftpro/
mad.call('ps_sftpro/ps.seq')
mad.call('ps_sftpro/ps_hs_sftpro.str')
mad.use(sequence='ps')
line = xt.Line.from_madx_sequence(mad.sequence.ps,
deferred_expressions=True)
Choose this approach only when you explicitly need to run MAD-X calculations
before importing; otherwise prefer the native xt.load parser above.