Fast lattice changes¶
Changes to beam line parameters can be applied using the python interface. For example to change the strength of a quadrupole one can use the following.
line['myquad'].knl[1] = 0.5
This approach works well when the change is applied to a small number of elements,
but can introduce a significant overhead on the simulation time when the number of
changes is very large. For these cases it is possible to use an xtrack.MultiSetter
to perform the changes in a more efficient way. The MultiSetter
stores the
memory addresses of the quantities to be changed and performs the changes with a single
compiled kernel, using multithreading when allowed by the context.
The following example shows how to use the MultiSetter
to apply a sinusoidal
ripple to the strength of several quadropoles of a synchrotron.
import numpy as np
from cpymad.madx import Madx
import xobjects as xo
import xtrack as xt
import xpart as xp
# Choose a context
ctx = xo.ContextCpu()
# Import SPS lattice and build a tracker
mad = Madx()
seq_name = 'sps'
mad.call('../../test_data/sps_w_spacecharge/sps_thin.seq')
mad.use(seq_name)
madtw = mad.twiss()
line = xt.Line.from_madx_sequence(mad.sequence[seq_name])
line.particle_ref = xp.Particles(p0c=400e9, mass0=xp.PROTON_MASS_EV)
tracker = line.build_tracker(_context=ctx)
# Switch on RF and twiss
line['acta.31637'].voltage = 7e9
line['acta.31637'].lag = 180.
twxt = tracker.twiss()
# Get revolution period
T_rev = twxt['T_rev']
# Extract list of elements to trim (all focusing quads)
elements_to_trim = [nn for nn in line.element_names if nn.startswith('qf.')]
# => contains ['qf.52010', 'qf.52210', 'qf.52410', 'qf.52610', 'qf.52810',
# 'qf.53010', 'qf.53210', 'qf.53410', 'qf.60010', 'qf.60210', ...]
# Build a custom setter
qf_setter = xt.MultiSetter(tracker, elements_to_trim,
field='knl', index=1 # we want to change knl[1]
)
# Get the initial values of the quad strength
k1l_0 = qf_setter.get_values()
# Generate particles to be tracked
# (we choose to match the distribution without accounting for spacecharge)
particles = xp.generate_matched_gaussian_bunch(_context=ctx,
num_particles=100, total_intensity_particles=1e10,
nemitt_x=3e-6, nemitt_y=3e-6, sigma_z=15e-2,
tracker=tracker)
# Define amplitude and phase of the quadrupole ripple
f_quad = 50. # Hz
A_quad = 0.01 # relative amplitude
# Track the particles and apply turn-by-turn change to all selected quads
num_turns = 2000
check_trim = []
for ii in range(num_turns):
if ii % 100 == 0: print(f'Turn {ii} of {num_turns}')
# Change the strength of the quads
k1l = k1l_0 * (1 + A_quad * np.sin(2*np.pi*f_quad*ii*T_rev))
qf_setter.set_values(k1l)
# Track one turn
tracker.track(particles)
# Log the strength of one quad to check
check_trim.append(ctx.nparray_from_context_array(line['qf.52010'].knl)[1])
# Plot the evolution of the quad strength
import matplotlib.pyplot as plt
plt.plot(check_trim)
plt.show()