Note
Go to the end to download the full example code or to run this example in your browser via Binder.
Tune Correction#
This tutorial shows how to read and correct the betatron tunes.
Prerequisites#
The tutorial requires an existing pyAML configuration file which includes configuration of the tune monitor and tune correction.
The example uses the configuration provided by the pyaml-test-lattice package.
from pyaml.common.constants import Action
from pyaml_test_lattice import configurations
Load the Accelerator#
from pyaml.accelerator import Accelerator
accelerator = Accelerator.load(configurations["pyaml/tango/pyaml-cs-oa/fodo_1gev_6d_pyaml-oa.yaml"])
# Print the string representation of the accelerator
print(accelerator)
Accelerator(facility='Synchrotron SOLEIL', machine='sr', simulators=['design'], controls=['live'])
Control Mode Choice#
accelerator.design— runs pyAT locally, no control system needed. Setwait_time = 0.0.accelerator.live— connects to the real machine or virtual twin. Setwait_timeto allow readback settling (typically 1.5–2 s).
For live control mode you need a running control system or virtual accelerator.
If you want to skip this, run the notebook in design` mode only.
# Use the design mode
SR = accelerator.design
# For live mode
# SR = accelerator.live
wait_time = 0.0 if SR == accelerator.design else 2.0
# Print the string representation of the simulator
print(SR)
Simulator(name='design', lattice='/opt/hostedtoolcache/Python/3.11.16/x64/lib/python3.11/site-packages/pyaml_test_lattice/data/lattice/fodo_1gev_6d.json', mat_key=None, n_elements=193)
Betatron Tune Monitor#
The tune monitor is defined in the configuration file under the name BETATRON_TUNE.
tune_monitor = SR.get_betatron_tune_monitor("BETATRON_TUNE")
# Print the string representation of the tune monitor
print(tune_monitor)
print(f"Current tune: {tune_monitor.tune.get()}")
BetatronTuneMonitor(name='BETATRON_TUNE', lattice_names='BETATRON_TUNE', peer=Simulator:design, tune_h='simulator/ringsimulator/ringsimulator/hor', tune_v='simulator/ringsimulator/ringsimulator/vert')
Current tune: [0.43994445 0.2848 ]
Quadrupolar Correctors#
The QForTune array contains the quadrupoles used for tune correction. You can access and set strengths of individual correctors.
qcorrectors = SR.magnets.get("QForTune")
first_q = qcorrectors[0]
print(f"The ring has {len(qcorrectors)} quadrupolar correctors. First corrector: {first_q.get_name()}")
# Print the string representation of the first quadrupole
print(qcorrectors[0])
# Get the current strength
str_before = qcorrectors[0].strength.get()
print(f"Current strength: {qcorrectors[0].strength.get()=:.4f}")
# Set the strength
qcorrectors[0].strength.set(str_before + 0.002)
print(f"After stepping by 0.002: {qcorrectors[0].strength.get()=:.4f}")
# Reset the strength
qcorrectors[0].strength.set(str_before)
print(f"Reset to {str_before:.4f}")
The ring has 32 quadrupolar correctors. First corrector: QF_001
Quadrupole(name='QF_001', lattice_names='QF_001', peer=Simulator:design, model=IdentityMagnetModel(physics='AN01-AR/EM-QP/QF.01/magnetic_strength', powerconverter=None, unit='1/m'), model_name='QF_001')
Current strength: qcorrectors[0].strength.get()=0.5032
After stepping by 0.002: qcorrectors[0].strength.get()=0.5052
Reset to 0.5032
Tune Correction#
SR.tune is the DEFAULT_TUNE_CORRECTION tool. SR.trm is the DEFAULT_TUNE_RESPONSE_MATRIX tool.
Before correcting the tune you need a response matrix. It can be measured as shown below or loaded from a previously saved file.
# Print the string representation of the tune correction tool
SR.tune
Tune(name='DEFAULT_TUNE_CORRECTION', lattice_names='DEFAULT_TUNE_CORRECTION', peer=Simulator:design, response_matrix=ndarray(shape=(2, 32), dtype=dtype('float64')), tune_monitor=BetatronTuneMonitor(name='BETATRON_TUNE', lattice_names='BETATRON_TUNE', peer=Simulator:design, tune_h='simulator/ringsimulator/ringsimulator/hor', tune_v='simulator/ringsimulator/ringsimulator/vert'), quadrupoles=[Quadrupole(name='QF_001', lattice_names='QF_001', peer=Simulator:design, model=IdentityMagnetModel, model_name='QF_001'), Quadrupole(name='QF_002', lattice_names='QF_002', peer=Simulator:design, model=IdentityMagnetModel, model_name='QF_002'), ... +29 more ..., Quadrupole(name='QD_016', lattice_names='QD_016', peer=Simulator:design, model=IdentityMagnetModel, model_name='QD_016')], quad_array_name='QForT...
Measure the Tune Response Matrix#
The callback below prints progress during the measurement. The sleep_between_step` parameter controls the wait time between corrector steps — set it to 0 for design mode.
Note: on some lattices disable_6d() is required before measuring in design mode since it only works in 4D.
# Required on some lattices before measuring the TRM in design mode
accelerator.design.get_lattice().disable_6d()
def tune_callback(action: int, cb_data: dict):
if action == Action.MEASURE:
print(f"Tune response: #{cb_data['step']} {cb_data['magnet']} {cb_data['tune']}")
return True
# Measure the response matrix
SR.trm.measure(sleep_between_step=wait_time, callback=tune_callback)
SR.trm.save("trm.json")
# Load the measured response matrix
SR.tune.load("trm.json")
print("Response matrix loaded.")
# Print string representation of the tune response matrix
print(SR.trm)
Tune response: #0 QF_001 [0.43943313 0.28496761]
Tune response: #0 QF_002 [0.43943313 0.28496761]
Tune response: #0 QF_003 [0.43943313 0.28496761]
Tune response: #0 QF_004 [0.43943313 0.28496761]
Tune response: #0 QF_005 [0.43943313 0.28496761]
Tune response: #0 QF_006 [0.43943313 0.28496761]
Tune response: #0 QF_007 [0.43943313 0.28496761]
Tune response: #0 QF_008 [0.43943313 0.28496761]
Tune response: #0 QF_009 [0.43943313 0.28496761]
Tune response: #0 QF_010 [0.43943313 0.28496761]
Tune response: #0 QF_011 [0.43943313 0.28496761]
Tune response: #0 QF_012 [0.43943313 0.28496761]
Tune response: #0 QF_013 [0.43943313 0.28496761]
Tune response: #0 QF_014 [0.43943313 0.28496761]
Tune response: #0 QF_015 [0.43943313 0.28496761]
Tune response: #0 QF_016 [0.43943313 0.28496761]
Tune response: #0 QD_001 [0.43984245 0.28540238]
Tune response: #0 QD_002 [0.43984245 0.28540238]
Tune response: #0 QD_003 [0.43984245 0.28540238]
Tune response: #0 QD_004 [0.43984245 0.28540238]
Tune response: #0 QD_005 [0.43984245 0.28540238]
Tune response: #0 QD_006 [0.43984245 0.28540238]
Tune response: #0 QD_007 [0.43984245 0.28540238]
Tune response: #0 QD_008 [0.43984245 0.28540238]
Tune response: #0 QD_009 [0.43984245 0.28540238]
Tune response: #0 QD_010 [0.43984245 0.28540238]
Tune response: #0 QD_011 [0.43984245 0.28540238]
Tune response: #0 QD_012 [0.43984245 0.28540238]
Tune response: #0 QD_013 [0.43984245 0.28540238]
Tune response: #0 QD_014 [0.43984245 0.28540238]
Tune response: #0 QD_015 [0.43984245 0.28540238]
Tune response: #0 QD_016 [0.43984245 0.28540238]
Response matrix loaded.
TuneResponseMatrix(name='DEFAULT_TUNE_RESPONSE_MATRIX', lattice_names='DEFAULT_TUNE_RESPONSE_MATRIX', peer=Simulator:design, latest_measurement={'type': 'pyaml.tuning_tools.response_matrix_data', 'matrix': [[0.5668682672334557, 0.5668682672327341, ... +29 more ..., 0.15754722402328003], [-0.16761322754133, -0.1676132275427733, ... +29 more ..., -0.6023777013410836]], ... +1 more ..., 'variable_names': ['QF_001', 'QF_002', ... +29 more ..., 'QD_016']}, quadrupoles=[Quadrupole(name='QF_001', lattice_names='QF_001', peer=Simulator:design, model=IdentityMagnetModel, model_name='QF_001'), Quadrupole(name='QF_002', lattice_names='QF_002', peer=Simulator:design, model=IdentityMagnetModel, model_name='QF_002'), ... +29 more ..., Quadrupole(name='QD_016', lattice_names='QD_016', peer=Simulator:desi...
Correcting the tune#
SR.tune.set([qx, qy]) runs the correction iteratively.
The iter parameter controls the number of iterations and wait_time the settling time between each iteration.
print(f"Tune before correction: {SR.tune.readback()}")
qx, qy = 0.19, 0.28
print(f"\nSetting tune to [{qx}, {qy}]")
SR.tune.set([qx, qy], iter=10, wait_time=wait_time)
print(f"Tune after correction: {SR.tune.readback()}")
qx, qy = 0.21, 0.30
print(f"\nSetting tune to [{qx}, {qy}]")
SR.tune.set([qx, qy], iter=10, wait_time=wait_time)
print(f"Tune after correction: {SR.tune.readback()}")
# Print the string representation of the tune correction
SR.tune
Tune before correction: [0.44 0.2848]
Setting tune to [0.19, 0.28]
Tune after correction: [0.19 0.28]
Setting tune to [0.21, 0.3]
Tune after correction: [0.21 0.3 ]
Tune(name='DEFAULT_TUNE_CORRECTION', lattice_names='DEFAULT_TUNE_CORRECTION', peer=Simulator:design, response_matrix=ndarray(shape=(2, 32), dtype=dtype('float64')), tune_monitor=BetatronTuneMonitor(name='BETATRON_TUNE', lattice_names='BETATRON_TUNE', peer=Simulator:design, tune_h='simulator/ringsimulator/ringsimulator/hor', tune_v='simulator/ringsimulator/ringsimulator/vert'), quadrupoles=[Quadrupole(name='QF_001', lattice_names='QF_001', peer=Simulator:design, model=IdentityMagnetModel, model_name='QF_001'), Quadrupole(name='QF_002', lattice_names='QF_002', peer=Simulator:design, model=IdentityMagnetModel, model_name='QF_002'), ... +29 more ..., Quadrupole(name='QD_016', lattice_names='QD_016', peer=Simulator:design, model=IdentityMagnetModel, model_name='QD_016')], quad_array_name='QForT...