Skip to content

Commit e04efa6

Browse files
authored
Merge pull request #222 from python-accelerator-middle-layer/fix-chromaticity-tool
Fix chroma tool + global moment compaction factor
2 parents 41df9f0 + 4032ab9 commit e04efa6

19 files changed

Lines changed: 444 additions & 325 deletions

examples/ESRF_ORM_example/measure_ideal_ORM.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,13 @@
55
from pyaml.tuning_tools.orbit_response_matrix import OrbitResponseMatrix
66

77
parent_folder = Path(__file__).parent
8-
config_path = parent_folder.parent.parent.joinpath(
9-
"tests", "config", "EBSOrbit.yaml"
10-
).resolve()
8+
config_path = parent_folder.parent.parent.joinpath("tests", "config", "EBSOrbit.yaml").resolve()
119
sr = Accelerator.load(config_path)
1210
ebs = sr.design
1311

14-
ebs.orm.measure()
15-
ebs.orm.save(parent_folder / Path("ideal_orm.json"))
16-
ebs.orm.save(parent_folder / Path("ideal_orm.yaml"), with_type="yaml")
17-
ebs.orm.save(parent_folder / Path("ideal_orm.npz"), with_type="npz")
12+
if ebs.orm.measure():
13+
ebs.orm.save(parent_folder / Path("ideal_orm.json"))
14+
ebs.orm.save(parent_folder / Path("ideal_orm.yaml"), with_type="yaml")
15+
ebs.orm.save(parent_folder / Path("ideal_orm.npz"), with_type="npz")
1816

1917
ormdata = ebs.orm.get()

pyaml/accelerator.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ class ConfigModel(BaseModel):
3333
energy : float
3434
Accelerator nominal energy. For ramped machine,
3535
this value can be dynamically set
36+
alphac : float, optional
37+
Moment compaction factor.
3638
controls : list[ControlSystem], optional
3739
List of control system used. An accelerator
3840
can access several control systems
@@ -53,6 +55,7 @@ class ConfigModel(BaseModel):
5355
facility: str
5456
machine: str
5557
energy: float
58+
alphac: float | None = None
5659
controls: list[ControlSystem] = None
5760
simulators: list[Simulator] = None
5861
data_folder: str
@@ -103,6 +106,9 @@ def __init__(self, cfg: ConfigModel):
103106
if cfg.energy is not None:
104107
self.set_energy(cfg.energy)
105108

109+
if cfg.alphac is not None:
110+
self.set_mcf(cfg.alphac)
111+
106112
self._yellow_pages = YellowPages(self)
107113

108114
self.post_init()
@@ -118,10 +124,26 @@ def set_energy(self, E: float):
118124
"""
119125
if self._cfg.simulators is not None:
120126
for s in self._cfg.simulators:
121-
s.set_energy(E)
127+
s._set_energy(E)
128+
if self._cfg.controls is not None:
129+
for c in self._cfg.controls:
130+
c._set_energy(E)
131+
132+
def set_mcf(self, alphac: float):
133+
"""
134+
Set the moment compaction factor for all simulators and control systems.
135+
136+
Parameters
137+
----------
138+
alphac : float
139+
Moment compaction factor
140+
"""
141+
if self._cfg.simulators is not None:
142+
for s in self._cfg.simulators:
143+
s._set_mcf(alphac)
122144
if self._cfg.controls is not None:
123145
for c in self._cfg.controls:
124-
c.set_energy(E)
146+
c._set_mcf(alphac)
125147

126148
def post_init(self):
127149
"""

pyaml/common/element.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,12 @@ def set_energy(self, E: float):
9797
"""
9898
pass
9999

100+
def set_mcf(self, alphac: float):
101+
"""
102+
Set the instrument moment compaction factor on this element
103+
"""
104+
pass
105+
100106
def check_peer(self):
101107
"""
102108
Throws an exception if the element is not attacched

pyaml/common/element_holder.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,3 +386,29 @@ def _list_diagnostics(self) -> list[str]:
386386
Return all diagnostic identifiers available in this holder.
387387
"""
388388
return list(self.__DIAG.keys())
389+
390+
def _set_energy(self, E: float):
391+
"""
392+
Sets the energy on all elements
393+
394+
Parameters
395+
----------
396+
E : float
397+
Energy in eV
398+
"""
399+
# Needed by energy dependant element (i.e. magnet coil current calculation)
400+
for m in self.get_all_elements():
401+
m.set_energy(E)
402+
403+
def _set_mcf(self, alphac: float):
404+
"""
405+
Sets the moment compaction factor on all elements
406+
407+
Parameters
408+
----------
409+
alphac : float
410+
Moment compaction factor
411+
"""
412+
# Needed by some off energy dependant element (i.e. chromaticty tools)
413+
for m in self.get_all_elements():
414+
m.set_mcf(alphac)

pyaml/control/controlsystem.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -162,19 +162,6 @@ def create_bpm_aggregators(self, bpms: list[BPM]) -> list[ScalarAggregator]:
162162
else:
163163
raise PyAMLException("Indexed BPM and scalar values cannot be mixed in the same array")
164164

165-
def set_energy(self, E: float):
166-
"""
167-
Sets the energy on magnets belonging to this control system
168-
169-
Parameters
170-
----------
171-
E : float
172-
Energy in eV
173-
"""
174-
# Needed by energy dependant element (i.e. magnet coil current calculation)
175-
for m in self.get_all_elements():
176-
m.set_energy(E)
177-
178165
def fill_device(self, elements: list[Element]):
179166
"""
180167
Fill device of this control system with Element

pyaml/diagnostics/chromaticity_monitor.py

Lines changed: 55 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from ..common.constants import Action
33
from ..common.element import ElementConfigModel
44
from ..common.exception import PyAMLException
5-
from ..tuning_tools.measurement_tool import MeasurementTool
5+
from ..tuning_tools.measurement_tool import MeasurementTool, MeasurementToolConfigModel
66

77
try:
88
from typing import Self # Python 3.11+
@@ -20,7 +20,7 @@
2020
PYAMLCLASS = "ChomaticityMonitor"
2121

2222

23-
class ConfigModel(ElementConfigModel):
23+
class ConfigModel(MeasurementToolConfigModel):
2424
"""
2525
Configuration model for Chromaticity Monitor.
2626
@@ -32,24 +32,13 @@ class ConfigModel(ElementConfigModel):
3232
Name of main RF frequency plant
3333
bpm_array_name : str,optional
3434
Name of main BPM array used for dispersion fit
35-
n_step : int, optional
36-
Default number of RF step during chromaticity
37-
measurement, by default 5
38-
alphac : float or None, optional
39-
Momentum compaction factor, by default None
4035
e_delta : float, optional
4136
Default variation of relative energy during chromaticity measurement:
4237
f0 - f0 * E_delta * alphac < f_RF < f0 + f0 * E_delta * alphac,
4338
by default 0.001
4439
max_e_delta : float, optional
4540
Maximum authorized variation of relative energy during chromaticity
4641
measurement, by default 0.004
47-
n_tune_meas : int, optional
48-
Default number of tune/orbit measurement per RF frequency, by default 1
49-
sleep_between_meas : float, optional
50-
Default sleep time in [s] between two tune measurements, by default 2.0
51-
sleep_between_step : float, optional
52-
Default sleep time in [s] after RF frequency variation, by default 5.0
5342
fit_order : int, optional
5443
Chomaticity fitting order, by default 1
5544
fit_disp_order : int, optional
@@ -63,13 +52,8 @@ class ConfigModel(ElementConfigModel):
6352
betatron_tune_name: str
6453
rf_plant_name: str
6554
bpm_array_name: str | None = None
66-
n_step: int = 5
67-
alphac: float | None = None
6855
e_delta: float = 0.001
6956
max_e_delta: float = 0.004
70-
n_tune_meas: int = 1
71-
sleep_between_meas: float = 0.0
72-
sleep_between_step: float = 0.0
7357
fit_order: int = 1
7458
fit_disp_order: int = 1
7559
fit_dispersion: bool = False
@@ -118,6 +102,7 @@ def __init__(self, cfg: ConfigModel):
118102
self._cfg = cfg
119103
self._chromaticity = RChromaDispArray(self, "chromaticity", "1")
120104
self._dipsersion = RChromaDispArray(self, "dispersion", "m")
105+
self._alphac = None
121106

122107
@property
123108
def chromaticity(self) -> ReadFloatArray:
@@ -127,10 +112,13 @@ def chromaticity(self) -> ReadFloatArray:
127112
Returns
128113
-------
129114
ReadFloatArray
130-
Array of chromaticity values [[q'x, q'y],[q''x, q''y],...]
115+
chromaticity values [q'x, q'y]
131116
"""
132117
return self._chromaticity
133118

119+
def set_mcf(self, alphac: float):
120+
self._alphac = alphac
121+
134122
@property
135123
def dispersion(self) -> ReadFloatArray:
136124
"""
@@ -149,7 +137,7 @@ def measure(
149137
alphac: float = None,
150138
e_delta: float = None,
151139
max_e_delta: float = None,
152-
n_tune_meas: int = None,
140+
n_avg_meas: int = None,
153141
sleep_between_meas: float = None,
154142
sleep_between_step: float = None,
155143
fit_order: int = None,
@@ -175,7 +163,7 @@ def measure(
175163
max_e_delta: float
176164
Maximum autorized variation of relative energy during chromaticity
177165
measurment [default: from config]
178-
n_tune_meas: int
166+
n_avg_meas: int
179167
Default number of tune/orbit measurment per RF frequency [default: from config]
180168
sleep_between_meas: float
181169
Default time sleep between two tune measurment [default: from config]
@@ -194,22 +182,24 @@ def measure(
194182
If the callback return false, then the process is aborted.
195183
"""
196184
n_step = n_step if n_step is not None else self._cfg.n_step
197-
alphac = alphac if alphac is not None else self._cfg.alphac
185+
alphac = alphac if alphac is not None else self._alphac
198186
e_delta = e_delta if e_delta is not None else self._cfg.e_delta
199187
max_e_delta = max_e_delta if max_e_delta is not None else self._cfg.max_e_delta
200-
n_tune_meas = n_tune_meas if n_tune_meas is not None else self._cfg.n_tune_meas
188+
n_avg_meas = n_avg_meas if n_avg_meas is not None else self._cfg.n_avg_meas
201189
sleep_between_meas = sleep_between_meas if sleep_between_meas is not None else self._cfg.sleep_between_meas
202190
sleep_between_step = sleep_between_step if sleep_between_step is not None else self._cfg.sleep_between_step
203191
fit_order = fit_order if fit_order is not None else self._cfg.fit_order
204192
fit_disp_order = fit_disp_order if fit_disp_order is not None else self._cfg.fit_disp_order
205193
fit_dispersion = fit_dispersion if fit_dispersion is not None else self._cfg.fit_dispersion
206194

207195
if abs(e_delta) > abs(max_e_delta):
208-
logger.warning("e_delta={e_delta} is greater than max_e_delta={max_e_delta}")
196+
logger.warning(f"e_delta={e_delta} is greater than max_e_delta={max_e_delta}")
209197

210198
if alphac is None:
211199
raise PyAMLException("Moment compaction factor is not defined")
212200

201+
self.register_callback(callback)
202+
213203
# Get devices
214204
self.check_peer()
215205
tm = self._peer.get_betatron_tune_monitor(self._cfg.betatron_tune_name)
@@ -234,55 +224,55 @@ def measure(
234224
# ensure that, even if there is an issus, the script will finish by
235225
# reseting the RF frequency to its original value
236226
err = None
237-
ok = True
227+
aborted = False
238228
try:
239229
for i, f in enumerate(delta_frec):
240230
# TODO : Use set_and_wait once it is implemented !
241231

242232
rf.frequency.set(f0 + f)
243-
244-
cb_data = {"step": i, "rf": f0 + f}
245-
if not self.send_callback(Action.APPLY, callback, cb_data):
246-
# Abort
247-
rf.frequency.set(f0)
248-
return False
233+
self.send_callback(Action.APPLY, {"step": i, "rf": float(f0 + f)})
249234
sleep(sleep_between_step)
250235

251236
# Averaging
252-
for j in range(n_tune_meas):
237+
for j in range(n_avg_meas):
253238
tune = tm.tune.get()
254239
Q[i] += tune
255-
cb_data = {"step": i, "avg_step": j, "rf": f0 + f, "tune": tune}
240+
cb_data = {"step": i, "avg_step": j, "rf": float(f0 + f), "tune": tune}
256241
if bpms is not None:
257242
orb = bpms.positions.get()
258243
orbit[i] += orb
259244
cb_data["orbit"] = orb
260-
if not self.send_callback(Action.MEASURE, callback, cb_data):
261-
# Abort
262-
rf.frequency.set(f0)
263-
return False
245+
self.send_callback(Action.MEASURE, cb_data)
264246

265-
if j < n_tune_meas - 1:
247+
if j < n_avg_meas - 1:
266248
sleep(sleep_between_meas)
267249

268-
Q /= float(n_tune_meas)
250+
Q /= float(n_avg_meas)
269251
if bpms is not None:
270-
orbit /= float(n_tune_meas)
252+
orbit /= float(n_avg_meas)
271253

272254
except Exception as ex:
273255
err = ex
256+
except KeyboardInterrupt as ex:
257+
aborted = True
274258
finally:
275-
# TODO : Use set_and_wait once it is implemented !
259+
# Restore
276260
rf.frequency.set(f0)
277-
cb_data = {"step": i, "rf": f0}
278-
ok = self.send_callback(Action.RESTORE, callback, cb_data)
261+
self.send_callback(Action.RESTORE, {"step": i, "rf": f0}, raiseException=False)
279262

280-
if err:
263+
if err is not None:
281264
raise (err)
282265

283-
self.fit(delta, Q, fit_order, orbit=orbit, fit_disp_order=fit_disp_order, do_plot=do_plot)
266+
if aborted:
267+
logger.warning(f"{self.get_name()} : measurement aborted")
268+
return False
269+
270+
if fit_dispersion:
271+
self.fit(delta, Q, fit_order, orbit=orbit, fit_disp_order=fit_disp_order, do_plot=do_plot)
272+
else:
273+
self.fit(delta, Q, fit_order, do_plot=do_plot)
284274

285-
return ok
275+
return True
286276

287277
def fit(self, deltas, Q, order, orbit=None, fit_disp_order=None, do_plot=False):
288278
"""
@@ -320,9 +310,15 @@ def fit(self, deltas, Q, order, orbit=None, fit_disp_order=None, do_plot=False):
320310
self.latest_measurement["dispersion"] = [dispx[:, 1], dispy[:, 1]] # First order dispersion
321311

322312
if do_plot:
323-
fig = plt.figure("Chromaticity_measurement")
313+
if fit_disp_order is None:
314+
fig = plt.figure("Chromaticity measurement")
315+
cols = 1
316+
else:
317+
fig = plt.figure("Chromaticity/Dispersion measurement")
318+
cols = 2
319+
324320
for i in range(2):
325-
ax = fig.add_subplot(2, 1, 1 + i)
321+
ax = fig.add_subplot(2, cols, 1 + i)
326322
ax.scatter(deltas * 100, Q[:, i])
327323
title = ""
328324
for o in range(order, -1, -1):
@@ -338,8 +334,17 @@ def fit(self, deltas, Q, order, orbit=None, fit_disp_order=None, do_plot=False):
338334

339335
ax.plot(deltas * 100, np.polyval(chroma[i][::-1], deltas))
340336
ax.set_title(title)
341-
ax.set_xlabel("Momentum Shift, dp/p [%]")
342337
ax.set_ylabel("%s Tune" % ["Horizontal", "Vertical"][i])
343-
# ax.legend()
338+
ax.set_xlabel("Momentum Shift, dp/p [%]")
339+
340+
if fit_disp_order is not None:
341+
ax = fig.add_subplot(2, cols, 3)
342+
ax.plot(dispx[:, 1])
343+
ax.set_ylabel("Dispersion [m]")
344+
ax = fig.add_subplot(2, cols, 4)
345+
ax.plot(dispy[:, 1])
346+
ax.set_xlabel("BPM #")
347+
ax.set_ylabel("Dispersion [m]")
348+
344349
fig.tight_layout()
345350
plt.show()

pyaml/lattice/simulator.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,6 @@ def get_description(self) -> str:
114114
"""
115115
return self._cfg.description
116116

117-
def set_energy(self, E: float):
118-
self.ring.energy = E
119-
# Needed by energy dependant element (i.e. magnet coil current calculation)
120-
for m in self.get_all_elements():
121-
m.set_energy(E)
122-
123117
def create_magnet_strength_aggregator(self, magnets: list[Magnet]) -> ScalarAggregator:
124118
# No magnet aggregator for simulator
125119
return None

0 commit comments

Comments
 (0)