Match
The Xtrack Line class provides a match method that allows using a numerical optimizer to adjust knobs attached to the line in order to obtain desired values in the twiss results (or as a result of other user-defined actions).
Basic usage
The numerical optimizer can be used calling the method xtrack.Line.match()
.
The optimization is define by a set of Vary and Target objects defining the
knobs to be varied and the targets to be matched. Arguments not specific of the
match method are automatically dispatched to the underlying twiss calls.
The following example shows how to match the tunes and chromaticities of a ring.
import xtrack as xt
# Load a line and build a tracker
line = xt.Line.from_json('../../test_data/hllhc15_thick/lhc_thick_with_knobs.json')
line.build_tracker()
# Match tunes and chromaticities to assigned values
opt = line.match(
method='4d', # <- passed to twiss
vary=[
xt.VaryList(['kqtf.b1', 'kqtd.b1'], step=1e-8, tag='quad'),
xt.VaryList(['ksf.b1', 'ksd.b1'], step=1e-4, limits=[-0.1, 0.1], tag='sext'),
],
targets = [
xt.TargetSet(qx=62.315, qy=60.325, tol=1e-6, tag='tune'),
xt.TargetSet(dqx=10.0, dqy=12.0, tol=0.01, tag='chrom'),
])
# Inspect optimization log
opt.log()
# prints:
#
# Table: 3 rows, 18 cols
# iteration penalty alpha tag tol_met target_active hit_limits vary_active vary_0 ...
# 0 12.9073 -1 nnnn yyyy nnnn yyyy 0
# 1 0.00270443 0 nnyy yyyy nnnn yyyy 4.2729e-05
# 2 1.22005e-06 0 yyyy yyyy nnnn yyyy 4.27163e-05
# Inspect optimization outcome
opt.target_status()
opt.vary_status()
# prints:
#
# Target status:
# id state tag tol_met residue current_val target_val description
# 0 ON tune True 8.53717e-11 62.315 62.315 'qx', val=62.315, tol=1e-06, weight=10)
# 1 ON tune True 1.49214e-13 60.325 60.325 'qy', val=60.325, tol=1e-06, weight=10)
# 2 ON chrom True 1.22005e-06 10 10 'dqx', val=10, tol=0.01, weight=1)
# 3 ON chrom True -1.87538e-09 12 12 'dqy', val=12, tol=0.01, weight=1)
# Vary status:
# id state tag name lower_limit current_val upper_limit val_at_iter_0 step weight
# 0 ON quad kqtf.b1 None 4.27163e-05 None 0 1e-08 1
# 1 ON quad kqtd.b1 None -4.27199e-05 None 0 1e-08 1
# 2 ON sext ksf.b1 -0.1 0.0118965 0.1 0 0.0001 1
# 3 ON sext ksd.b1 -0.1 -0.0232137 0.1 0 0.0001 1
# Get knob values after optimization
knobs_after_match = opt.get_knob_values()
# contains: {'kqtf.b1': 4.27163e-05, 'kqtd.b1': -4.27199e-05,
# 'ksf.b1': 0.0118965, 'ksd.b1': -0.0232137}
# Get knob values before optimization
knobs_before_match = opt.get_knob_values(iteration=0)
# contains: {'kqtf.b1': 0, 'kqtd.b1': 0, 'ksf.b1': 0, 'ksd.b1': 0}
# Complete source: xtrack/examples/match/000_match_basic.py
Match at specific locations
See also xtrack.Line.match()
The match method can also be used on a portion of a beam line and/or with
targets at specific locations. By default the provided boundary conditions are
imposed at the start of the specified range. The constants xt.START
and
xt.END
can be used to specify targets at the start and end of the range
respectively. This is illustrated in the following example, showing how to
match a closed orbit bump in a given beamline range.
import xtrack as xt
# Load a line and build a tracker
line = xt.Line.from_json('../../test_data/hllhc15_thick/lhc_thick_with_knobs.json')
line.build_tracker()
opt = line.match(
start='mq.30l8.b1', end='mq.23l8.b1',
betx=1, bety=1, y=0, py=0, # <-- conditions at start
vary=xt.VaryList(['acbv30.l8b1', 'acbv28.l8b1', 'acbv26.l8b1', 'acbv24.l8b1'],
step=1e-10, limits=[-1e-3, 1e-3]),
targets = [
xt.TargetSet(y=3e-3, py=0, at='mb.b28l8.b1'),
xt.TargetSet(y=0, py=0, at=xt.END)
])
opt.target_status()
# prints:
#
# Target status:
# id state tag tol_met residue current_val target_val description
# 0 ON True 1.30104e-18 0.003 0.003 ('y', 'mb.b28l8.b1'), val=0.003, tol=1e- ...
# 1 ON True -3.38813e-20 -3.38813e-20 0 ('py', 'mb.b28l8.b1'), val=0, tol=1e-10, ...
# 2 ON True -4.127e-17 -4.127e-17 0 ('y', 'mq.23l8.b1'), val=0, tol=1e-10, w ...
# 3 ON True -6.1664e-19 -6.1664e-19 0 ('py', 'mq.23l8.b1'), val=0, tol=1e-10, ...
# Complete source: xtrack/examples/match/002_match_bump_basic.py
Alternatively, the boundary conditions can be imposed at the end or within the specified range as illustrated in the following examples.
import xtrack as xt
# Load a line and build a tracker
line = xt.Line.from_json('../../test_data/hllhc15_thick/lhc_thick_with_knobs.json')
line.build_tracker()
opt = line.match(
start='mq.30l8.b1', end='mq.23l8.b1',
init_at=xt.END, betx=1, bety=1, y=0, py=0, # <-- conditions at end
vary=xt.VaryList(['acbv30.l8b1', 'acbv28.l8b1', 'acbv26.l8b1', 'acbv24.l8b1'],
step=1e-10, limits=[-1e-3, 1e-3]),
targets = [
xt.TargetSet(y=3e-3, py=0, at='mb.b28l8.b1'),
xt.TargetSet(y=0, py=0, at=xt.START)
])
opt.target_status()
# prints:
#
# Target status:
# id state tag tol_met residue current_val target_val description
# 0 ON True 1.30104e-18 0.003 0.003 ('y', 'mb.b28l8.b1'), val=0.003, tol=1e- ...
# 1 ON True -3.38813e-20 -3.38813e-20 0 ('py', 'mb.b28l8.b1'), val=0, tol=1e-10, ...
# 2 ON True -4.127e-17 -4.127e-17 0 ('y', 'mq.23l8.b1'), val=0, tol=1e-10, w ...
# 3 ON True -6.1664e-19 -6.1664e-19 0 ('py', 'mq.23l8.b1'), val=0, tol=1e-10, ...
# Complete source: xtrack/examples/match/002a_match_bump_init_end.py
import xtrack as xt
# Load a line and build a tracker
line = xt.Line.from_json('../../test_data/hllhc15_thick/lhc_thick_with_knobs.json')
line.build_tracker()
opt = line.match(
start='mq.30l8.b1', end='mq.23l8.b1',
init_at='mb.b28l8.b1', betx=1, bety=1, y=3e-3, py=0, # <-- conditions at point inside the range
vary=xt.VaryList(['acbv30.l8b1', 'acbv28.l8b1', 'acbv26.l8b1', 'acbv24.l8b1'],
step=1e-10, limits=[-1e-3, 1e-3]),
targets = [
xt.TargetSet(y=0, py=0, at=xt.START),
xt.TargetSet(y=0, py=0, at=xt.END)
])
opt.target_status()
# prints:
#
# Target status:
# id state tag tol_met residue current_val target_val description
# 0 ON True -5.68188e-18 -5.68188e-18 0 ('y', 'mq.30l8.b1'), val=0, tol=1e-10, w ...
# 1 ON True -8.0272e-20 -8.0272e-20 0 ('py', 'mq.30l8.b1'), val=0, tol=1e-10, ...
# 2 ON True 1.03651e-18 1.03651e-18 0 ('y', 'mq.23l8.b1'), val=0, tol=1e-10, w ...
# 3 ON True -9.48677e-20 -9.48677e-20 0 ('py', 'mq.23l8.b1'), val=0, tol=1e-10, ...
# Complete source: xtrack/examples/match/002b_match_bump_init_middle.py
Boundary conditions and target values from existing table
See also xtrack.Line.match()
Boundary conditions and target values used for the matching can be obtained also from an existing TwissTable object, as illustrated in the following example.
import xtrack as xt
# Load a line and build a tracker
line = xt.Line.from_json('../../test_data/hllhc15_thick/lhc_thick_with_knobs.json')
line.build_tracker()
tw0 = line.twiss(method='4d')
opt = line.match(
start='mq.30l8.b1', end='mq.23l8.b1',
init=tw0, init_at=xt.END, # <-- Boundary conditions from table
vary=xt.VaryList(['acbv30.l8b1', 'acbv28.l8b1', 'acbv26.l8b1', 'acbv24.l8b1'],
step=1e-10, limits=[-1e-3, 1e-3]),
targets = [
xt.TargetSet(y=3e-3, py=0, at='mb.b28l8.b1'),
xt.TargetSet(['y', 'py'], value=tw0, at=xt.START) # <-- Target from table
])
opt.target_status()
# prints:
#
# Target status:
# id state tag tol_met residue current_val target_val description
# 0 ON True 1.30104e-18 0.003 0.003 ('y', 'mb.b28l8.b1'), val=0.003, tol=1e- ...
# 1 ON True -3.38813e-20 -3.38813e-20 0 ('py', 'mb.b28l8.b1'), val=0, tol=1e-10, ...
# 2 ON True -4.127e-17 -4.127e-17 0 ('y', 'mq.23l8.b1'), val=0, tol=1e-10, w ...
# 3 ON True -6.1664e-19 -6.1664e-19 0 ('py', 'mq.23l8.b1'), val=0, tol=1e-10, ...
# Complete source: xtrack/examples/match/003_match_bump_from_table.py
Match involving multiple lines
See also xtrack.Line.match()
The match method can also be used to match multiple lines at the same time. This is illustrated in the following example, showing how to match orbit bumps in the two beams of a collider to obtain a given crossing angle between the two beams. Some of the used dipole magnets are shared between the two beams.
import xtrack as xt
# Load a line and build a tracker
collider = xt.Multiline.from_json(
'../../test_data/hllhc15_thick/hllhc15_collider_thick.json')
collider.build_trackers()
tw0 = collider.twiss(method='4d')
opt = collider.match(
lines=['lhcb1', 'lhcb2'],
start=['e.ds.l5.b1', 'e.ds.l5.b2'],
end=['s.ds.r5.b1', 's.ds.r5.b2'],
init=tw0,
vary=xt.VaryList([
'acbxv1.r5', 'acbxv1.l5', # <-- common elements
'acbyvs4.l5b1', 'acbrdv4.r5b1', 'acbcv5.l5b1', # <-- b1
'acbyvs4.l5b2', 'acbrdv4.r5b2', 'acbcv5.r5b2', # <-- b2
],
step=1e-10, limits=[-1e-3, 1e-3]),
targets = [
xt.TargetSet(y=0, py=10e-6, at='ip5', line='lhcb1'),
xt.TargetSet(y=0, py=-10e-6, at='ip5', line='lhcb2'),
xt.TargetSet(y=0, py=0, at=xt.END, line='lhcb1'),
xt.TargetSet(y=0, py=0, at=xt.END, line='lhcb2')
])
opt.target_status()
# prints:
#
# Target status:
# id state tag tol_met residue current_val target_val description
# 0 ON True -3.93023e-19 -3.93023e-19 0 line=lhcb1, ('y', 'ip5'), val=0, tol=1e- ...
# 1 ON True -7.06934e-18 1e-05 1e-05 line=lhcb1, ('py', 'ip5'), val=1e-05, to ...
# 2 ON True -1.76183e-19 -1.76183e-19 0 line=lhcb2, ('y', 'ip5'), val=0, tol=1e- ...
# 3 ON True -1.07353e-17 -1e-05 -1e-05 line=lhcb2, ('py', 'ip5'), val=-1e-05, t ...
# 4 ON True 4.39323e-18 4.39323e-18 0 line=lhcb1, ('y', 's.ds.r5.b1'), val=0, ...
# 5 ON True 2.00777e-19 2.00777e-19 0 line=lhcb1, ('py', 's.ds.r5.b1'), val=0, ...
# 6 ON True 5.23202e-19 5.23202e-19 0 line=lhcb2, ('y', 's.ds.r5.b2'), val=0, ...
# 7 ON True -4.05091e-20 -4.05091e-20 0 line=lhcb2, ('py', 's.ds.r5.b2'), val=0, ...
# Complete source: xtrack/examples/match/004_match_bump_common_elements.py
Callables and inequalities in targets
See also xtrack.Line.match()
Targets can contain also callables and inequalities. This is illustrated in the following example, showing the match of crossing bump (as in the previous section) where we use a callable to match the average angle at the IP to zero and inequalities to impose a minimum and maximum value for the angle of one beam at the IP.
import xtrack as xt
# Load a line and build a tracker
collider = xt.Multiline.from_json(
'../../test_data/hllhc15_thick/hllhc15_collider_thick.json')
collider.build_trackers()
tw0 = collider.twiss(method='4d')
opt = collider.match(
lines=['lhcb1', 'lhcb2'],
start=['e.ds.l5.b1', 'e.ds.l5.b2'],
end=['s.ds.r5.b1', 's.ds.r5.b2'],
init=tw0,
vary=xt.VaryList([
'acbxv1.r5', 'acbxv1.l5', # <-- common elements
'acbyvs4.l5b1', 'acbrdv4.r5b1', 'acbcv5.l5b1', # <-- b1
'acbyvs4.l5b2', 'acbrdv4.r5b2', 'acbcv5.r5b2', # <-- b2
],
step=1e-10, limits=[-1e-3, 1e-3]),
targets = [
xt.Target(y=0, at='ip5', line='lhcb1'),
xt.Target('py', xt.GreaterThan(9e-6), at='ip5', line='lhcb1'), # <-- inequality
xt.Target('py', xt.LessThan( 11e-6), at='ip5', line='lhcb1'), # <-- inequality
xt.Target(y=0, at='ip5', line='lhcb2'),
xt.Target(
lambda tw: tw.lhcb1['py', 'ip5'] + tw.lhcb2['py', 'ip5'], value=0), # <-- callable
xt.TargetSet(y=0, py=0, at=xt.END, line='lhcb1'),
xt.TargetSet(y=0, py=0, at=xt.END, line='lhcb2')
])
opt.target_status()
# prints:
#
# Target status:
# id state tag tol_met residue current_val target_val description
# 0 ON True -5.42101e-20 -5.42101e-20 0 line=lhcb1, ('y', 'ip5'), val=0, tol=1e- ...
# 1 ON True -1.99849e-17 9e-06 GreaterThan(9e-06) line=lhcb1, ('py', 'ip5'), val=GreaterTh ...
# 2 ON True 0 9e-06 LessThan(1.1e-05) line=lhcb1, ('py', 'ip5'), val=LessThan( ...
# 3 ON True -4.67562e-19 -4.67562e-19 0 line=lhcb2, ('y', 'ip5'), val=0, tol=1e- ...
# 4 ON True 1.03338e-19 1.03338e-19 0 callable, val=0, tol=1e-10, weight=1
# 5 ON True 3.64674e-18 3.64674e-18 0 line=lhcb1, ('y', 's.ds.r5.b1'), val=0, ...
# 6 ON True 1.68179e-19 1.68179e-19 0 line=lhcb1, ('py', 's.ds.r5.b1'), val=0, ...
# 7 ON True 2.05694e-18 2.05694e-18 0 line=lhcb2, ('y', 's.ds.r5.b2'), val=0, ...
# 8 ON True -1.21224e-19 -1.21224e-19 0 line=lhcb2, ('py', 's.ds.r5.b2'), val=0, ...
# Complete source: xtrack/examples/match/005_match_bump_common_ele_callable_ineq.py
Matching on results of arbitrary actions
See also xtrack.Line.match()
By default the quantities used as match targets are found in the result of the
twiss method. It is nevertheless possible to use the match method on results of
arbitrary user-defined “actions”. Each action is defined by writing a small python
class inheriting from xtrack.Action
providing a method called run
,
called at each optimization step, which returns a dictionary of quantities that
can be used as targets. This is illustrated by the following example, showing
how to use octupole magnets to control the detuning with amplitude coefficients
(det_xx = dqx/dJx
and det_yy = dqy/dJy
) as obtained by tracking.
import xtrack as xt
# Load a line and build a tracker
line = xt.Line.from_json('../../test_data/hllhc15_thick/lhc_thick_with_knobs.json')
line.build_tracker()
class ActionMeasAmplDet(xt.Action):
def __init__(self, line, num_turns, nemitt_x, nemitt_y):
self.line = line
self.num_turns = num_turns
self.nemitt_x = nemitt_x
self.nemitt_y = nemitt_y
def run(self):
det_coefficients = self.line.get_amplitude_detuning_coefficients(
nemitt_x=self.nemitt_x, nemitt_y=self.nemitt_y,
num_turns=self.num_turns)
out = {'d_xx': det_coefficients['det_xx'],
'd_yy': det_coefficients['det_yy']}
return out
action = ActionMeasAmplDet(line=line, nemitt_x=2.5e-6, nemitt_y=2.5e-6,
num_turns=128)
opt = line.match(vary=xt.VaryList(['kof.a23b1', 'kod.a23b1'], step=1.),
targets=[action.target('d_xx', 1000., tol=0.1),
action.target('d_yy', 2000., tol=0.1)])
opt.target_status()
# prints:
#
# Target status:
# id state tag tol_met residue current_val target_val description
# 0 ON True 0.0844456 1000.08 1000 'd_xx', val=1000, ...
# 1 ON True -0.00209987 2000 2000 'd_yy', val=2000, ...
# Complete source: xtrack/examples/match/006_match_action.py
Interactive match
See also xtrack.Line.match()
The match method can also be used in an interactive way passing solve=False
to the xtrack.Line.match()
. In this case an xdeps.Optimize
object
is returned that can be used to interactively drive the optimization process,
by enabling/disabling knobs and targets, changing target values and tolerances,
controlling the number of optimization steps, tagging and reloading specific
optimization steps. This is illustrated in the following example.
import xtrack as xt
# Load a line and build a tracker
line = xt.Line.from_json('../../test_data/hllhc15_thick/lhc_thick_with_knobs.json')
line.build_tracker()
# Build optimizer object for tunes and chromaticities without performing optimization
opt = line.match(
solve=False, # <--
method='4d',
vary=[
xt.VaryList(['kqtf.b1', 'kqtd.b1'], step=1e-8, tag='quad'),
xt.VaryList(['ksf.b1', 'ksd.b1'], step=1e-4, limits=[-0.1, 0.1], tag='sext'),
],
targets = [
xt.TargetSet(qx=62.315, qy=60.325, tol=1e-6, tag='tune'),
xt.TargetSet(dqx=10.0, dqy=12.0, tol=0.01, tag='chrom'),
])
opt.target_status()
# prints the following (optimization not performed):
#
# Target status:
# id state tag tol_met residue current_val target_val description
# 0 ON tune False -0.00499997 62.31 62.315 'qx', val=62.315, tol=1e-06, weight=10)
# 1 ON tune False -0.005 60.32 60.325 'qy', val=60.325, tol=1e-06, weight=10)
# 2 ON chrom False -8.09005 1.90995 10 'dqx', val=10, tol=0.01, weight=1)
# 3 ON chrom False -10.057 1.94297 12 'dqy', val=12, tol=0.01, weight=1)
# Disable optimization of chromaticities and usage of sextupole knobs
opt.disable_targets(tag='chrom')
opt.disable_vary(tag='sext')
opt.show()
# prints:
#
# Vary:
# id tag state description
# 0 quad ON name='kqtf.b1', limits=None, step=1e-08, weight=1)
# 1 quad ON name='kqtd.b1', limits=None, step=1e-08, weight=1)
# 2 sext OFF name='ksf.b1', limits=(-0.1, 0.1), step=0.0001, weight=1)
# 3 sext OFF name='ksd.b1', limits=(-0.1, 0.1), step=0.0001, weight=1)
# Targets:
# id tag state description
# 0 tune ON 'qx', val=62.315, tol=1e-06, weight=10)
# 1 tune ON 'qy', val=60.325, tol=1e-06, weight=10)
# 2 chrom OFF 'dqx', val=10, tol=0.01, weight=1)
# 3 chrom OFF 'dqy', val=12, tol=0.01, weight=1)
# Solve (for tunes only)
opt.solve()
opt.target_status()
# prints:
# Target status:
# id state tag tol_met residue current_val target_val description
# 0 ON tune True -4.51905e-12 62.315 62.315 'qx', val=62.315, tol=1e-06, weight=10)
# 1 ON tune True 9.23706e-14 60.325 60.325 'qy', val=60.325, tol=1e-06, weight=10)
# 2 OFF chrom False 0 1.89374 10 'dqx', val=10, tol=0.01, weight=1)
# 3 OFF chrom False 0 1.91882 12 'dqy', val=12, tol=0.01, weight=1)
# Enable all targets and knobs
opt.enable_all_targets()
opt.enable_all_vary()
# Solve (for tunes and chromaticities)
opt.solve()
opt.target_status()
# prints:
# Target status:
# id state tag tol_met residue current_val target_val description
# 0 ON tune True -5.62885e-10 62.315 62.315 'qx', val=62.315, tol=1e-06, weight=10)
# 1 ON tune True 2.67875e-11 60.325 60.325 'qy', val=60.325, tol=1e-06, weight=10)
# 2 ON chrom True -0.000156234 9.99984 10 'dqx', val=10, tol=0.01, weight=1)
# 3 ON chrom True -9.81714e-07 12 12 'dqy', val=12, tol=0.01, weight=1)
# Change a target value and the corresponding tolerance
opt.targets[1].value = 60.05
opt.targets[1].tol = 1e-10
opt.target_status()
# prints:
# Target status:
# id state tag tol_met residue current_val target_val description
# 0 ON tune True -5.62885e-10 62.315 62.315 'qx', val=62.315, tol=1e-06, weight=10)
# 1 ON tune False 0.275 60.325 60.05 'qy', val=60.05, tol=1e-10, weight=10)
# 2 ON chrom True -0.000156234 9.99984 10 'dqx', val=10, tol=0.01, weight=1)
# 3 ON chrom True -9.81714e-07 12 12 'dqy', val=12, tol=0.01, weight=1)
# Perform two optimization steps (without checking for convergence)
opt.step(2)
opt.target_status()
# prints (two steps were not enough to reach convergence):
# Target status:
# id state tag tol_met residue current_val target_val description
# 0 ON tune True 4.55631e-08 62.315 62.315 'qx', val=62.315, tol=1e-06, weight=10)
# 1 ON tune False 2.56767e-09 60.05 60.05 'qy', val=60.05, tol=1e-10, weight=10)
# 2 ON chrom True -0.000127644 9.99987 10 'dqx', val=10, tol=0.01, weight=1)
# 3 ON chrom True -2.44325e-05 12 12 'dqy', val=12, tol=0.01, weight=1)
# Perform additional two steps
opt.step(2)
opt.target_status()
# prints (convergence was reached):
#
# Target status:
# id state tag tol_met residue current_val target_val description
# 0 ON tune True -4.00533e-11 62.315 62.315 'qx', val=62.315, tol=1e-06, weight=10)
# 1 ON tune True 1.42109e-14 60.05 60.05 'qy', val=60.05, tol=1e-10, weight=10)
# 2 ON chrom True 3.19579e-07 10 10 'dqx', val=10, tol=0.01, weight=1)
# 3 ON chrom True -1.30694e-09 12 12 'dqy', val=12, tol=0.01, weight=1)
# Tag present configuration
opt.tag(tag='my_tag')
# Reload initial configuration
opt.reload(iteration=0)
opt.target_status()
# prints:
#
# Target status:
# id state tag tol_met residue current_val target_val description
# 0 ON tune False -0.00499997 62.31 62.315 'qx', val=62.315, tol=1e-06, weight=10
# 1 ON tune False 0.27 60.32 60.05 'qy', val=60.05, tol=1e-10, weight=10
# 2 ON chrom False -8.09005 1.90995 10 'dqx', val=10, tol=0.01, weight=1
# 3 ON chrom False -10.057 1.94297 12 'dqy', val=12, tol=0.01, weight=1
# Reload tagged configuration
opt.reload(tag='my_tag')
opt.target_status()
# Target status:
# id state tag tol_met residue current_val target_val description
# 0 ON tune True -4.00533e-11 62.315 62.315 'qx', val=62.315, tol=1e-06, weight=10
# 1 ON tune True 1.42109e-14 60.05 60.05 'qy', val=60.05, tol=1e-10, weight=10
# 2 ON chrom True 3.19579e-07 10 10 'dqx', val=10, tol=0.01, weight=1
# 3 ON chrom True -1.30694e-09 12 12 'dqy', val=12, tol=0.01, weight=1
# Complete source: xtrack/examples/match/001_match_interactive.py
Create new knobs by matching
See also xtrack.Line.match_knob()
The xtrack.Line.match_knob()
method allows generating new knobs based on
the result of an optimization. The user can specify a value for
knob_value_start
corresponding to the line state before the optimization,
and a value for knob_value_end
corresponding to the line state after the
optimization. A linear interpolation is used when a different value of the knob
is set. This shown by the following example, which shows how to build knobs
controlling the horizontal and vertical chromaticities of a line.
import xtrack as xt
# Load a line and build a tracker
line = xt.Line.from_json('../../test_data/hllhc15_thick/lhc_thick_with_knobs.json')
line.build_tracker()
tw0 = line.twiss(method='4d')
# Knob optimizer for horizontal chromaticity
opt = line.match_knob('dqx.b1', knob_value_start=tw0.dqx, knob_value_end=3.0,
run=False, method='4d',
vary=xt.VaryList(['ksf.b1', 'ksd.b1'], step=1e-8),
targets=xt.TargetSet(dqx=3.0, dqy=tw0, tol=1e-6))
# New terms have been added to knobs to vary
line.vars['ksf.b1']._expr # is: (0.0 + vars['ksf.b1_from_dqx.b1'])
line.vars['ksd.b1']._expr # is: (0.0 + vars['ksd.b1_from_dqx.b1'])
line.vars['ksf.b1_from_dqx.b1']._expr # is None
line.vars['ksd.b1_from_dqx.b1']._expr # is None
# optimized acts on newly created terms
opt.vary_status(); opt.target_status()
# prints:
#
# Vary status:
# id state tag name lower_limit current_val upper_limit val_at_iter_0 step weight
# 0 ON ksf.b1_from_dqx.b1 None 0 None 0 1e-08 1
# 1 ON ksd.b1_from_dqx.b1 None 0 None 0 1e-08 1
# Target status:
# id state tag tol_met residue current_val target_val description
# 0 ON False -1.09005 1.90995 3 'dqx', val=3, tol=1e-06, weight=1
# 1 ON True 0 1.94297 1.94297 'dqy', val=1.94297, tol=1e-06, weight=1
opt.solve() # perform optimization
opt.vary_status(); opt.target_status()
# prints:
#
# Vary status:
# id state tag name lower_limit current_val upper_limit val_at_iter_0 step weight
# 0 ON ksf.b1_from_dqx.b1 None 0.00130336 None 0 1e-08 1
# 1 ON ksd.b1_from_dqx.b1 None -0.0004024 None 0 1e-08 1
# Target status:
# id state tag tol_met residue current_val target_val description
# 0 ON True 7.94672e-08 3 3 'dqx', val=3, tol=1e-06, weight=1
# 1 ON True 0 1.94297 1.94297 'dqy', val=1.94297, tol=1e-06, weight=1
# Generate the knob
opt.generate_knob()
line.vars['ksf.b1']._expr # is: (0.0 + vars['ksf.b1_from_dqx.b1'])
line.vars['ksd.b1']._expr # is: (0.0 + vars['ksd.b1_from_dqx.b1'])
line.vars['ksf.b1_from_dqx.b1']._expr
# is ((0.0011956933485755728 * vars['dqx.b1']) - 0.0022837181704350494)
line.vars['ksd.b1_from_dqx.b1']._expr # is None
# is ((-0.0003691583859286993 * vars['dqx.b1']) - -0.0007050751889840094)
# Create also vertical chromaticity knob
opt_dqy = line.match_knob('dqy.b1', knob_value_start=tw0.dqy, knob_value_end=3.0,
run=False, method='4d',
vary=xt.VaryList(['ksf.b1', 'ksd.b1'], step=1e-8),
targets=xt.TargetSet(dqx=tw0, dqy=3.0, tol=1e-6))
opt_dqy.solve()
opt_dqy.generate_knob()
line.vars['ksf.b1']._expr
# is: ((0.0 + vars['ksf.b1_from_dqx.b1']) + vars['ksf.b1_from_dqy.b1'])
line.vars['ksd.b1']._expr
# is: ((0.0 + vars['ksd.b1_from_dqx.b1']) + vars['ksd.b1_from_dqy.b1'])
line.vars['ksf.b1_from_dqx.b1']._expr
# is ((0.0011956933485755728 * vars['dqx.b1']) - 0.0022837181704350494)
line.vars['ksd.b1_from_dqx.b1']._expr # is None
# is ((-0.0003691583859286993 * vars['dqx.b1']) - -0.0007050751889840094)
line.vars['ksf.b1_from_dqy.b1']._expr
# is ((0.0011956933485755728 * vars['dqy.b1']) - 0.0022837181704350494)
line.vars['ksd.b1_from_dqy.b1']._expr
# is ((-0.0003691583859286993 * vars['dqy.b1']) - -0.0007050751889840094)
# Test knobs
line.vars['dqx.b1'] = 5.
line.vars['dqy.b1'] = 6.
tw = line.twiss(method='4d')
tw.dqx # is 5.00000231
tw.dqy # is 5.99999987
# Complete source: xtrack/examples/match/007_match_knob.py
Targets from variables and from line elements
Targets for optimization can be defined also from variables and from from the lines, as illustrated in the following example.
import xtrack as xt
# Load a line and build a tracker
collider = xt.Multiline.from_json(
'../../test_data/hllhc15_thick/hllhc15_collider_thick.json')
collider.build_trackers()
tw0 = collider.twiss(method='4d')
twb1 = collider.lhcb1.twiss(start='e.ds.l5.b1', end='s.ds.r5.b1', init=tw0.lhcb1)
twb2 = collider.lhcb2.twiss(start='e.ds.l5.b2', end='s.ds.r5.b2', init=tw0.lhcb2)
vars = collider.vars
line_b1 = collider.lhcb1
opt = collider.match(
solve=False,
vary=xt.VaryList([
'acbxv1.r5', 'acbxv1.l5', # <-- common elements
'acbyvs4.l5b1', 'acbrdv4.r5b1', 'acbcv5.l5b1', 'acbcv6.r5b1', # <-- b1
'acbyvs4.l5b2', 'acbrdv4.r5b2', 'acbcv5.r5b2', 'acbcv6.l5b2' # <-- b2
],
step=1e-10, limits=[-1e-3, 1e-3]),
targets = [
# Targets from b1 twiss
twb1.target(y=0, py=10e-6, at='ip5'),
twb1.target(y=0, py=0, at=xt.END),
# Targets from b2 twiss
twb2.target(y=0, py=-10e-6, at='ip5'),
twb2.target(['y', 'py'], at=xt.END), # <-- preserve
# Targets from vars
vars.target('acbxv1.l5', xt.LessThan(1e-3)),
vars.target('acbxv1.l5', xt.GreaterThan(1e-6)),
vars.target(lambda vv: vv['acbxv1.l5'] + vv['acbxv1.r5'], xt.LessThan(1e-9)),
# Targets from line
line_b1.target(lambda ll: ll['mcbrdv.4r5.b1'].ksl[0], xt.GreaterThan(1e-6)),
line_b1.target(lambda ll: ll['mcbxfbv.a2r5'].ksl[0] + ll['mcbxfbv.a2l5'].ksl[0],
xt.LessThan(1e-9)),
])
opt.solve()
opt.target_status()
# prints:
#
# Target status:
# id state tag tol_met residue current_val target_val description
# 0 ON True 6.95245e-17 6.95245e-17 0 line=lhcb1, ('y', 'ip5'), val=0, tol=1e- ...
# 1 ON True -9.7917e-19 1e-05 1e-05 line=lhcb1, ('py', 'ip5'), val=1e-05, to ...
# 2 ON True -3.83958e-16 -3.83958e-16 0 line=lhcb1, ('y', 's.ds.r5.b1'), val=0, ...
# 3 ON True -1.73842e-17 -1.73842e-17 0 line=lhcb1, ('py', 's.ds.r5.b1'), val=0, ...
# 4 ON True -3.81775e-17 -3.81775e-17 0 line=lhcb2, ('y', 'ip5'), val=0, tol=1e- ...
# 5 ON True -1.5128e-18 -1e-05 -1e-05 line=lhcb2, ('py', 'ip5'), val=-1e-05, t ...
# 6 ON True -3.66729e-17 -3.66729e-17 0.0 line=lhcb2, ('y', 's.ds.r5.b2'), val=0, ...
# 7 ON True 1.35054e-18 1.35054e-18 -0.0 line=lhcb2, ('py', 's.ds.r5.b2'), val=-0 ...
# 8 ON True 0 1e-06 LessThan(0.001) 'acbxv1.l5', val=LessThan(0.001), tol=1e ...
# 9 ON True -8.47033e-22 1e-06 GreaterThan(1e-06) 'acbxv1.l5', val=GreaterThan(1e-06), tol ...
# 10 ON True 0 1e-09 LessThan(1e-09) callable, val=LessThan(1e-09), tol=1e-10 ...
# 11 ON True 0 1.02889e-06 GreaterThan(1e-06) line=lhcb1, callable, val=GreaterThan(1e ...
# 12 ON True 0 1e-09 LessThan(1e-09) line=lhcb1, callable, val=LessThan(1e-09 ...
# Complete source: xtrack/examples/match/005a_match_bump_common_targets_from_table.py
Match and twiss with symmetry constraints on one boundary
In some cases, it is useful to twiss or match a line with periodicity constraints on one of the boundary and symmetry constraints on the other boundary, for example to design a symmetric cell in a periodic lattice. This can be achived by building only one half of the period and passing init=”periodic_symmetric” to the match and twiss methods, as illustrated in the following example.
import xtrack as xt
import xobjects as xo
import numpy as np
# Build line with half a cell
half_cell = xt.Line(
elements={
'start_cell': xt.Marker(),
'drift0': xt.Drift(length=1.),
'qf1': xt.Quadrupole(k1=0.027/2, length=1.),
'drift1_1': xt.Drift(length=1),
'bend1': xt.Bend(k0=3e-4, h=3e-4, length=45.),
'drift1_2': xt.Drift(length=1.),
'qd1': xt.Quadrupole(k1=-0.0271/2, length=1.),
'drift2': xt.Drift(length=1),
'mid_cell': xt.Marker(),
}
)
half_cell.particle_ref = xt.Particles(p0c=2e9)
# Add observation points every 1 m (to see betas inside bends)
half_cell.discard_tracker()
s_cut = np.arange(0, half_cell.get_length(), 1.)
half_cell.cut_at_s(s_cut)
# Attach knobs to quadrupoles
half_cell.vars['kqf'] = 0.027/2
half_cell.vars['kqd'] = -0.0271/2
half_cell.element_refs['qf1'].k1 = half_cell.vars['kqf']
half_cell.element_refs['qd1'].k1 = half_cell.vars['kqd']
# Match with periodic symmetric boundary
opt_halfcell = half_cell.match(
method='4d',
start='start_cell', end='mid_cell',
init='periodic_symmetric',
targets=xt.TargetSet(mux=0.2501/2, muy=0.2502/2, at='mid_cell'),
vary=xt.VaryList(['kqf', 'kqd'], step=1e-5),
)
# Twiss with periodic symmetric boundary
tw_half_cell = half_cell.twiss4d(init='periodic_symmetric')
# Plot
import matplotlib.pyplot as plt
plt.close('all')
tw_half_cell.plot()
plt.show()
# Complete source: xtrack/examples/symm_twiss_and_match/000_symm_twiss_and_match.py