Skip to content

Commit b680964

Browse files
JoschDYannis Angelis
andauthored
SbS Dispersion Fix (#516)
* Propagable dpxy for segment dispersion propagation * Fix forward propagation of dispersion * Introduce dpx,dpy in the fake measurement * SPS test data with dpx,dpy * Test momentum dispersion for sbs * version bump and zenodo --------- Co-authored-by: Yannis Angelis <[email protected]>
1 parent f8ee06f commit b680964

File tree

15 files changed

+2879
-2752
lines changed

15 files changed

+2879
-2752
lines changed

.zenodo.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
"name": "OMC-Team",
55
"affiliation": "CERN"
66
},
7+
{
8+
"name": "Yannis Angelis",
9+
"affiliation": "CERN",
10+
"orcid": "0009-0003-9057-5165"
11+
},
712
{
813
"name": "Felix Simon Carlier",
914
"affiliation": "CERN",
@@ -65,6 +70,10 @@
6570
{
6671
"name": "Andreas Wegscheider",
6772
"affiliation": "CERN"
73+
},
74+
{
75+
"name": "Wietse Van Goethem",
76+
"affiliation": "CERN"
6877
}
6978
],
7079
"title": "OMC3",

CHANGELOG.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# OMC3 Changelog
22

3+
#### 2025-08-26 - v0.24.5 - _yangelis_
4+
5+
- Fixed/Added:
6+
- Segement-by-Segment dispersion propagation by adding momentum dispersion propagable.
7+
38
#### 2025-04-28 - v0.24.4 - _wvangoet_
49

510
- Fixed:
@@ -346,7 +351,7 @@
346351
(over moving average window)
347352
- Action error calculated from error on the spectral line
348353
(which in turn is the same as NOISE)
349-
354+
350355
#### 2022-11-01 - v0.6.6
351356

352357
- Bugfixes:
@@ -477,7 +482,7 @@
477482
- more usages of constants for strings (e.g. column names)
478483
- introducing pathlib.Path in some places
479484
- output-paths in model job-files are relative
480-
485+
481486
- Fixed:
482487
- Matplotlib warnings for `set_window_title`
483488
- excluded Windows and MacOS py3.9 from normal testing, due to installation issues of pyTables

omc3/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
__title__ = "omc3"
1212
__description__ = "An accelerator physics tools package for the OMC team at CERN."
1313
__url__ = "https://github.com/pylhc/omc3"
14-
__version__ = "0.24.4"
14+
__version__ = "0.24.5"
1515
__author__ = "pylhc"
1616
__author_email__ = "[email protected]"
1717
__license__ = "MIT"

omc3/optics_measurements/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
DRIVEN_PHASE_NAME: str = f"{PHASE_NAME}driven_"
2121
DRIVEN_TOTAL_PHASE_NAME: str = f"{TOTAL_PHASE_NAME}driven_"
2222
DISPERSION_NAME: str = "dispersion_"
23+
MOMENTUM_DISPERSION_NAME: str = 'momentum_dispersion_' # Segment-by-Segment only
2324
NORM_DISP_NAME: str = "normalised_dispersion_"
2425
ORBIT_NAME: str = "orbit_"
2526
KICK_NAME: str = "kick_"
@@ -66,6 +67,7 @@
6667
ORBIT: str = "" # Column is plane (X or Y) in files
6768
CLOSED_ORBIT: str = "CO"
6869
DISPERSION: str = "D"
70+
MOMENTUM_DISPERSION: str = "DP"
6971
NORM_DISPERSION: str = "ND"
7072

7173
MEASUREMENT: str = "MEAS"

omc3/scripts/fake_measurement_from_model.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
IMAG,
9494
MDL,
9595
MODEL_DIRECTORY,
96+
MOMENTUM_DISPERSION,
9697
NAME,
9798
NAME2,
9899
NORM_DISP_NAME,
@@ -281,6 +282,11 @@ def create_dispersion(df_twiss: pd.DataFrame, df_model: pd.DataFrame, parameter:
281282
plane = parameter[-1]
282283
df = create_measurement(df_twiss, parameter, relative_error, randomize)
283284
df = append_model_param(df, df_model, parameter)
285+
286+
df_dp = create_measurement(df_twiss, f'{MOMENTUM_DISPERSION}{plane}', relative_error, randomize)
287+
df_dp = append_model_param(df_dp, df_model, f'{MOMENTUM_DISPERSION}{plane}')
288+
df = tfs.concat([df, df_dp], axis=1, join='inner')
289+
284290
df = append_model_s_and_phaseadv(df, df_model, planes=plane)
285291
df.headers = headers.copy()
286292
return {f'{DISPERSION_NAME}{plane.lower()}': df}

omc3/segment_by_segment/propagables/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
from omc3.segment_by_segment.propagables.alpha import AlphaPhase
1717
from omc3.segment_by_segment.propagables.beta import BetaPhase
1818
from omc3.segment_by_segment.propagables.coupling import F1001, F1010
19-
from omc3.segment_by_segment.propagables.dispersion import Dispersion
19+
from omc3.segment_by_segment.propagables.dispersion import Dispersion, MomentumDispersion
2020
from omc3.segment_by_segment.propagables.phase import Phase
2121
from omc3.segment_by_segment.propagables.utils import PropagableColumns # noqa: F401 -> expose
2222

23-
ALL_PROPAGABLES: tuple[type[Propagable], ...] = (Phase, BetaPhase, AlphaPhase, Dispersion, F1001, F1010)
23+
ALL_PROPAGABLES: tuple[type[Propagable], ...] = (Phase, BetaPhase, AlphaPhase, Dispersion, MomentumDispersion, F1001, F1010)

omc3/segment_by_segment/propagables/dispersion.py

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99

1010
from typing import TYPE_CHECKING
1111

12-
from omc3.optics_measurements.constants import DISPERSION
12+
import numpy as np
13+
from pandas import Series
14+
15+
from omc3.optics_measurements.constants import DISPERSION, MOMENTUM_DISPERSION
1316
from omc3.segment_by_segment import math as sbs_math
1417
from omc3.segment_by_segment.propagables.abstract import Propagable
1518
from omc3.segment_by_segment.propagables.phase import Phase
@@ -119,3 +122,97 @@ def _compute_elements(self, plane: str, seg_model: pd.DataFrame, forward: bool):
119122
model_phase = Phase.get_segment_phase(seg_model, plane, forward)
120123
propagated_err = sbs_math.propagate_error_dispersion(model_disp, model_phase, init_condition)
121124
return model_disp, propagated_err
125+
126+
127+
class MomentumDispersion(Propagable):
128+
129+
_init_pattern = "dp{}_{}" # format(plane, ini/end)
130+
columns: PropagableColumns = PropagableColumns(MOMENTUM_DISPERSION)
131+
132+
@classmethod
133+
def get_at(cls, names: IndexType, meas: OpticsMeasurement, plane: str) -> ValueErrorType:
134+
c = cls.columns.planed(plane)
135+
momentum_dispersion = meas.dispersion[plane].loc[names, c.column]
136+
model_momentum_dispersion_err = np.nan # No error in the measured dpx,dpy
137+
return momentum_dispersion, model_momentum_dispersion_err
138+
139+
@classmethod
140+
def in_measurement(cls, meas: OpticsMeasurement) -> bool:
141+
""" Check if the dispersion is in the measurement data. """
142+
try:
143+
meas.dispersion_x
144+
meas.dispersion_y
145+
except FileNotFoundError:
146+
return False
147+
return True
148+
149+
def init_conditions_dict(self):
150+
# needs to be inverted for backward propagation, i.e. the end-init
151+
init_cond = super().init_conditions_dict()
152+
for key, value in init_cond.items():
153+
if "end" in key:
154+
init_cond[key] = -value
155+
return init_cond
156+
157+
def add_differences(self, segment_diffs: SegmentDiffs):
158+
dfs = self.get_difference_dataframes()
159+
for plane, df in dfs.items():
160+
# save to diffs/write to file (if allow_write is set)
161+
segment_diffs.momentum_dispersion[plane] = df
162+
163+
def _compute_measured(self,
164+
plane: str,
165+
seg_model: TfsDataFrame,
166+
forward: bool
167+
) -> tuple[pd.Series, pd.Series]:
168+
""" Compute the momentum dispersion difference between the given segment model and the measured values."""
169+
170+
# get the measured values
171+
names = self.get_segment_observation_points(plane)
172+
momentum_dispersion, err_pdisp = self.get_at(names, self._meas, plane)
173+
174+
# get the propagated values
175+
model_momentum_dispersion = seg_model.loc[names, f"{MOMENTUM_DISPERSION}{plane}"]
176+
177+
if not forward:
178+
model_momentum_dispersion = -model_momentum_dispersion
179+
180+
# calculate difference
181+
momentum_dispersion_diff = momentum_dispersion - model_momentum_dispersion
182+
183+
propagated_err = Series(np.nan, index=model_momentum_dispersion.index) # No error propagation for now
184+
return momentum_dispersion_diff, propagated_err
185+
186+
def _compute_correction(
187+
self,
188+
plane: str,
189+
seg_model: pd.DataFrame,
190+
seg_model_corr: pd.DataFrame,
191+
forward: bool,
192+
) -> tuple[pd.Series, pd.Series]:
193+
"""Compute the momentum dispersion differennce between the nominal and the corrected model."""
194+
195+
model_momentum_dispersion = seg_model.loc[:, f"{MOMENTUM_DISPERSION}{plane}"]
196+
corrected_momentum_dispersion = seg_model_corr.loc[:, f"{MOMENTUM_DISPERSION}{plane}"]
197+
198+
momentum_dispersion_diff = corrected_momentum_dispersion - model_momentum_dispersion
199+
if not forward:
200+
momentum_dispersion_diff = -momentum_dispersion_diff
201+
202+
propagated_err = Series(np.nan, index=model_momentum_dispersion.index) # No error propagation for now
203+
return momentum_dispersion_diff, propagated_err
204+
205+
def _compute_elements(self, plane: str, seg_model: pd.DataFrame, forward: bool):
206+
""" Compute get the propagated momentum dispersion values from the segment model and calculate the propagated error. """
207+
208+
model_momentum_dispersion = seg_model.loc[:, f"{MOMENTUM_DISPERSION}{plane}"]
209+
210+
pderror = Series(np.nan, index=model_momentum_dispersion.index) # No error propagation for now
211+
return model_momentum_dispersion, pderror
212+
213+
def get_segment_observation_points(self, plane: str):
214+
""" Return the measurement points for the given plane, that are in the segment. """
215+
return common_indices(
216+
self.segment_models.forward.index,
217+
self._meas.dispersion[plane].index
218+
)

omc3/segment_by_segment/segments.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
F1001_NAME,
2121
F1010_NAME,
2222
KMOD_BETA_NAME,
23+
MOMENTUM_DISPERSION_NAME,
2324
NORM_DISP_NAME,
2425
PHASE_NAME,
2526
)
@@ -117,6 +118,7 @@ class SegmentDiffs(TfsCollection):
117118
beta_kmod = Tfs(f"{PREFIX}{KMOD_BETA_NAME}{{plane}}_{{name}}{EXT}")
118119
beta_amp = Tfs(f"{PREFIX}{AMP_BETA_NAME}{{plane}}_{{name}}{EXT}")
119120
dispersion = Tfs(f"{PREFIX}{DISPERSION_NAME}{{plane}}_{{name}}{EXT}")
121+
momentum_dispersion = Tfs(f"{PREFIX}{MOMENTUM_DISPERSION_NAME}{{plane}}_{{name}}{EXT}")
120122
norm_dispersion = Tfs(f"{PREFIX}{NORM_DISP_NAME}{{plane}}_{{name}}{EXT}")
121123
f1001 = Tfs(f"{PREFIX}{F1001_NAME}_{{name}}{EXT}", two_planes=False)
122124
f1010 = Tfs(f"{PREFIX}{F1010_NAME}_{{name}}{EXT}", two_planes=False)

tests/accuracy/test_sbs.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
AlphaPhase,
2626
BetaPhase,
2727
Dispersion,
28+
MomentumDispersion,
2829
Phase,
2930
PropagableColumns,
3031
)
@@ -59,7 +60,8 @@ class TestSbSLHC:
5960
Phase: TestCfg(1e-2, 1e-8, 5e-4, "phase"),
6061
BetaPhase: TestCfg(9e-2, 1e-10, 5e-4, "beta_phase"),
6162
AlphaPhase: TestCfg(1e-1, 1e-8, 8e-2, "alpha_phase"),
62-
Dispersion: TestCfg(1e-2, None, None, "dispersion"), # not really working at the moment, see https://github.com/pylhc/omc3/issues/498
63+
Dispersion: TestCfg(1e-6, 1e-4, 1e-4, "dispersion"),
64+
MomentumDispersion: TestCfg(1e-7, 1e-4, 1e-4, "momentum_dispersion"),
6365
F1001: TestCfg(5e-4, 5e-7, None, "f1001"),
6466
F1010: TestCfg(5e-4, 5e-6, None, "f1010"),
6567
}
@@ -87,7 +89,7 @@ def test_lhc_segment_creation(self,
8789
"beam": beam,
8890
"nat_tunes": [0.31, 0.32],
8991
"dpp": 0.0,
90-
"energy": 6500,
92+
"energy": 6800,
9193
"modifiers": [acc_models_lhc_2025 / OPTICS_SUBDIR / OPTICS_30CM_FLAT],
9294
}
9395

@@ -203,8 +205,6 @@ def test_lhc_propagation_sbs(self,
203205
]
204206

205207
for propagable in ALL_PROPAGABLES:
206-
if propagable is Dispersion:
207-
continue # TODO: not working, see https://github.com/pylhc/omc3/issues/498
208208

209209
cfg: TestCfg = self.config_map[propagable]
210210
planes = [REAL, IMAG, AMPLITUDE, PHASE] if propagable.is_rdt() else "xy"
@@ -230,6 +230,13 @@ def test_lhc_propagation_sbs(self,
230230
INPUT_SBS / f"measurement_b{beam}" / f"{beta_file}_{plane}.tfs",
231231
index=NAME
232232
)
233+
elif propagable is MomentumDispersion:
234+
# momentum dispersion is in the dispersion-measurement file
235+
dispersion_file = self.config_map[Dispersion].file_name
236+
meas_df = tfs.read(
237+
INPUT_SBS / f"measurement_b{beam}" / f"{dispersion_file}_{plane}.tfs",
238+
index=NAME
239+
)
233240
else:
234241
meas_df = tfs.read(
235242
INPUT_SBS / f"measurement_b{beam}" / f"{full_file_name}.tfs",
@@ -264,7 +271,6 @@ def test_lhc_propagation_sbs(self,
264271
assert sbs_df[col].abs().min() < self.eps_rdt_min # for RDTs the init value is calculated,
265272
else:
266273
assert sbs_df[col].abs().min() == 0 # at least the first entry should show no difference
267-
268274
assert sbs_df[col].abs().max() > cfg.diff_max
269275
assert sbs_df[err_col].all()
270276

@@ -277,7 +283,7 @@ def test_lhc_propagation_sbs(self,
277283
# TODO: check backward coupling, see https://github.com/pylhc/omc3/issues/498
278284
continue
279285

280-
if propagable in [Phase, F1001, F1010]: # check absolute difference
286+
if propagable in [Phase, F1001, F1010, Dispersion, MomentumDispersion]: # check absolute difference
281287
assert_all_close(sbs_df, correction, col, atol=eps)
282288
assert_all_close(sbs_df, expected, 0, atol=eps)
283289
else: # check relative difference

0 commit comments

Comments
 (0)