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. Set wait_time = 0.0.

  • accelerator.live — connects to the real machine or virtual twin. Set wait_time to 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...

Gallery generated by Sphinx-Gallery