Skip to content

Commit 89b01d6

Browse files
awegscheJoschD
andauthored
Acc-Models Creation (#432)
- Added: - complete overhaul of model creation, uses now acc-models for LHC, PS and PSB and prints useful information about available model parameters. Can load from either a user defined path (--path <PATH>) or from the afs copy of acc-models (--afs) --------- Co-authored-by: JoschD <[email protected]>
1 parent 455b013 commit 89b01d6

27 files changed

+4125
-327
lines changed

CHANGELOG.md

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

3+
#### 2023-12-07 - v0.13.0 - _awegsche_
4+
5+
- Added:
6+
- complete overhaul of model creation, uses now `acc-models` for LHC, PS and PSB and prints
7+
useful information about available model parameters. Can load from either a user defined path
8+
(`--path <PATH>`) or from the afs copy of acc-models (`--afs`)
9+
310
#### 2023-11-29 - v0.12.1 - _jdilly_
411

512
- Fixed:

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.12.1"
14+
__version__ = "0.13.0"
1515
__author__ = "pylhc"
1616
__author_email__ = "[email protected]"
1717
__license__ = "MIT"

omc3/model/accelerators/accelerator.py

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
It contains entrypoint the parent `Accelerator` class as well as other support classes.
77
"""
88
import re
9+
import os
910
from pathlib import Path
1011
from typing import List, Union, Sequence
1112

@@ -16,7 +17,7 @@
1617

1718
from omc3.model.constants import (
1819
ERROR_DEFFS_TXT,
19-
JOB_MODEL_MADX,
20+
JOB_MODEL_MADX_NOMINAL,
2021
MODIFIER_TAG,
2122
MODIFIERS_MADX,
2223
TWISS_AC_DAT,
@@ -59,6 +60,8 @@ class Accelerator:
5960
AccElementTypes.ARC_BPMS: r".*",
6061
}
6162
BPM_INITIAL = "B"
63+
NAME=None
64+
REPOSITORY=None
6265

6366
@staticmethod
6467
def get_parameters():
@@ -95,11 +98,11 @@ def get_parameters():
9598
params.add_parameter(
9699
name="energy",
97100
type=float,
98-
help="Energy in Tev.",
101+
help="Energy in GeV.",
99102
)
100103
params.add_parameter(
101104
name="modifiers",
102-
type=Path,
105+
type=PathOrStr,
103106
nargs="*",
104107
help="Path to the optics file to use (modifiers file).",
105108
)
@@ -118,6 +121,7 @@ def __init__(self, opt) -> None:
118121
self.model_best_knowledge = None
119122
self.elements = None
120123
self.error_defs_file = None
124+
self.acc_model_path = None
121125
self.modifiers = None
122126
self._beam_direction = 1
123127
self._beam = None
@@ -137,11 +141,7 @@ def __init__(self, opt) -> None:
137141
else:
138142
self.init_from_options(opt)
139143

140-
def init_from_options(self, opt) -> None:
141-
if opt.nat_tunes is None:
142-
raise AcceleratorDefinitionError("Argument 'nat_tunes' is required.")
143-
if (opt.drv_tunes is None) and (opt.driven_excitation is not None):
144-
raise AcceleratorDefinitionError("Argument 'drv_tunes' is required.")
144+
def init_from_options(self, opt):
145145
self.nat_tunes = opt.nat_tunes
146146

147147
if opt.driven_excitation is not None:
@@ -193,6 +193,17 @@ def init_from_model_dir(self, model_dir: Path) -> None:
193193
if best_knowledge_path.is_file():
194194
self.model_best_knowledge = tfs.read(best_knowledge_path, index="NAME")
195195

196+
# Base Model ########################################
197+
if self.REPOSITORY is not None:
198+
acc_models = model_dir / self.REPOSITORY
199+
200+
if acc_models.is_dir():
201+
if acc_models.is_symlink():
202+
self.acc_model_path = Path(os.readlink(acc_models)).absolute()
203+
else:
204+
self.acc_model_path = acc_models
205+
# else this wasn't an acc-models based model
206+
196207
# Modifiers #########################################
197208
self.modifiers = _get_modifiers_from_modeldir(model_dir)
198209

@@ -256,7 +267,12 @@ def verify_object(self):
256267
"""
257268
Verifies that this instance of an `Accelerator` is properly instantiated.
258269
"""
259-
raise NotImplementedError("A function should have been overwritten, check stack trace.")
270+
# since we removed `required` args, we check here if everything has been passed
271+
if self.model_dir is None:
272+
if self.nat_tunes is None:
273+
raise AttributeError("Natural tunes not set (missing `--nat_tunes` flag?)")
274+
if self.excitation != AccExcitationMode.FREE and self.drv_tunes is None:
275+
raise AttributeError("Driven excitation selected but no driven tunes given (missing `--drv_tunes` flag?)")
260276

261277
def get_exciter_bpm(self, plane: str, commonbpms: List[str]):
262278
"""
@@ -355,13 +371,16 @@ class AcceleratorDefinitionError(Exception):
355371

356372
def _get_modifiers_from_modeldir(model_dir: Path) -> List[Path]:
357373
"""Parse modifiers from job.create_model.madx or use modifiers.madx file."""
358-
job_file = model_dir / JOB_MODEL_MADX
374+
job_file = model_dir / JOB_MODEL_MADX_NOMINAL
359375
if job_file.exists():
360376
job_madx = job_file.read_text()
361377

362378
# find modifier tag in lines and return called file in these lines
379+
# the modifier tag is used by the model creator to mark which line defines modifiers
380+
# see e.g. `get_base_madx_script()` in `lhc.py`
381+
# example for a match to the regex: `call, file = 'modifiers.madx'; MODIFIER_TAG`
363382
modifiers = re.findall(
364-
fr"\s+call,\s*file\s*=\s*[\"\']?([^;\'\"]+)[\"\']?\s*;\s*{MODIFIER_TAG}",
383+
fr"\s*call,\s*file\s*=\s*[\"\']?([^;\'\"]+)[\"\']?\s*;\s*{MODIFIER_TAG}",
365384
job_madx,
366385
flags=re.IGNORECASE,
367386
)

omc3/model/accelerators/lhc.py

Lines changed: 89 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ class Lhc(Accelerator):
113113
"""
114114

115115
NAME = "lhc"
116+
REPOSITORY = "acc-models-lhc"
116117
RE_DICT: Dict[str, str] = {
117118
AccElementTypes.BPMS: r"BPM",
118119
AccElementTypes.MAGNETS: r"M",
@@ -132,15 +133,23 @@ def get_parameters():
132133
params.add_parameter(
133134
name="year",
134135
type=str,
135-
required=True,
136-
choices=("2012", "2015", "2016", "2017", "2018", "2022", "hllhc1.3"),
137136
help="Year of the optics (or hllhc1.x version).",
138137
)
139138
params.add_parameter(
140139
name="ats",
141140
action="store_true",
142141
help="Force use of ATS macros and knobs for years which are not ATS by default.",
143-
)
142+
)
143+
params.add_parameter(
144+
name="b2_errors",
145+
type=str,
146+
help="The B2 error table to load for the best knowledge model.",
147+
)
148+
params.add_parameter(
149+
name="list_b2_errors",
150+
action="store_true",
151+
help="Lists all available b2 error tables",
152+
)
144153
return params
145154

146155
def __init__(self, *args, **kwargs):
@@ -150,36 +159,38 @@ def __init__(self, *args, **kwargs):
150159
self.correctors_dir = "2012"
151160
self.year = opt.year
152161
self.ats = opt.ats
162+
self.b2_errors = opt.b2_errors
163+
self.list_b2_errors = opt.list_b2_errors
153164
if self.year == "hllhc1.3":
154165
self.correctors_dir = "hllhc1.3"
155166
self.beam = opt.beam
156167
beam_to_beam_direction = {1: 1, 2: -1}
157168
self.beam_direction = beam_to_beam_direction[self.beam]
158-
self.verify_object()
159169

160170
def verify_object(self) -> None: # TODO: Maybe more checks?
161171
"""
162172
Verifies if everything is defined which should be defined.
163173
Will Raise an ``AcceleratorDefinitionError`` if one of the checks is invalid.
164174
"""
165175
LOGGER.debug("Accelerator class verification")
176+
177+
Accelerator.verify_object(self)
166178
_ = self.beam
167179

168180
if self.model_dir is None and self.xing is None:
169181
raise AcceleratorDefinitionError("Crossing on or off not set.")
170182

171-
if self.excitation is None:
172-
raise AcceleratorDefinitionError("Excitation mode not set.")
173-
if (self.excitation != AccExcitationMode.FREE) and (self.drv_tunes is None):
174-
raise AcceleratorDefinitionError("An excitation mode was given but driven tunes are not set.")
175-
176183
# TODO: write more output prints
177-
LOGGER.debug("... verification passed. \nSome information about the accelerator:")
184+
LOGGER.debug(
185+
"... verification passed. \nSome information about the accelerator:"
186+
)
178187
LOGGER.debug(f"Class name {self.__class__.__name__}")
179188
LOGGER.debug(f"Beam {self.beam}")
180189
LOGGER.debug(f"Beam direction {self.beam_direction}")
181190
if self.modifiers:
182-
LOGGER.debug(f"Modifiers {', '.join([str(m) for m in self.modifiers])}")
191+
LOGGER.debug(
192+
f"Modifiers {', '.join([str(m) for m in self.modifiers])}"
193+
)
183194

184195
@property
185196
def beam(self) -> int:
@@ -216,7 +227,11 @@ def get_variables(self, frm: float = None, to: float = None, classes: Iterable[s
216227
LOGGER.debug("The following classes are not found as corrector/variable classes and "
217228
f"are assumed to be the variable names directly instead:\n{str(unknown_classes)}")
218229

219-
vars = list(set(_flatten_list(all_vars_by_class[corr_cls] for corr_cls in known_classes)))
230+
vars = list( set(
231+
_flatten_list(
232+
all_vars_by_class[corr_cls]
233+
for corr_cls in known_classes))
234+
)
220235
vars = vars + unknown_classes
221236

222237
# Sort variables by S (nice for comparing different files)
@@ -297,6 +312,11 @@ def log_status(self) -> None:
297312
LOGGER.info(f"> Driven Tune Y [{self.drv_tunes[1]:10.3f}]")
298313

299314
def load_main_seq_madx(self) -> str:
315+
if self.acc_model_path is not None:
316+
main_call = f'call, file = \'{self.acc_model_path / "lhc.seq"}\';'
317+
if self.year.startswith('hl'):
318+
main_call += f'\ncall, file = \'{self.acc_model_path / "hllhc_sequence.madx"}\';'
319+
return main_call
300320
try:
301321
return _get_call_main_for_year(self.year)
302322
except AttributeError:
@@ -323,19 +343,28 @@ def get_exciter_bpm(self, plane: str, commonbpms: List[str]):
323343
if self.excitation == AccExcitationMode.ACD:
324344
try:
325345
return (
326-
_is_one_of_in([f"BPMY{a_b}.6L4.B{beam}", f"BPM.7L4.B{beam}"], commonbpms),
346+
_is_one_of_in(
347+
[f"BPMY{a_b}.6L4.B{beam}", f"BPM.7L4.B{beam}"], commonbpms
348+
),
327349
f"MKQA.6L4.B{beam}",
328350
)
329351
except KeyError as e:
330-
raise KeyError("AC-Dipole BPM not found in the common BPMs. Maybe cleaned?") from e
352+
raise KeyError(
353+
"AC-Dipole BPM not found in the common BPMs. Maybe cleaned?"
354+
) from e
331355
if self.excitation == AccExcitationMode.ADT:
332356
try:
333357
return (
334-
_is_one_of_in([f"BPMWA.B5{l_r}4.B{beam}", f"BPMWA.A5{l_r}4.B{beam}"], commonbpms),
358+
_is_one_of_in(
359+
[f"BPMWA.B5{l_r}4.B{beam}", f"BPMWA.A5{l_r}4.B{beam}"],
360+
commonbpms,
361+
),
335362
f"ADTK{adt}5{l_r}4.B{beam}",
336363
)
337364
except KeyError as e:
338-
raise KeyError("ADT BPM not found in the common BPMs. Maybe cleaned?") from e
365+
raise KeyError(
366+
"ADT BPM not found in the common BPMs. Maybe cleaned?"
367+
) from e
339368
return None
340369

341370
def important_phase_advances(self) -> List[List[str]]:
@@ -379,34 +408,46 @@ def get_base_madx_script(self, best_knowledge: bool = False) -> str:
379408
high_beta = False
380409
madx_script = (
381410
f"{self._get_madx_script_info_comments()}"
382-
f"! ----- Calling Sequence and Optics -----\n"
383411
f"call, file = '{self.model_dir / MACROS_DIR / GENERAL_MACROS}';\n"
384412
f"call, file = '{self.model_dir / MACROS_DIR / LHC_MACROS}';\n"
385-
)
413+
)
414+
madx_script += f"omc3_beam_energy = {self.energy};\n"
415+
madx_script += "exec, define_nominal_beams();\n\n"
386416
if self._uses_run3_macros():
387-
LOGGER.debug("According to the optics year, Run 3 versions of the macros will be used")
417+
LOGGER.debug(
418+
"According to the optics year, Run 3 versions of the macros will be used"
419+
)
388420
madx_script += (
389421
f"call, file = '{self.model_dir / MACROS_DIR / LHC_MACROS_RUN3}';\n"
390422
)
391423

392-
madx_script += (
393-
f"{self.load_main_seq_madx()}\n"
394-
f"exec, define_nominal_beams();\n"
395-
)
424+
madx_script += "! ----- Calling Sequence and Optics -----\n"
425+
madx_script += "option, -echo; ! suppress output from base sequence loading to keep the log small\n"
426+
madx_script += self.load_main_seq_madx()
427+
madx_script += "\n\n"
428+
396429
if self.modifiers is not None:
397430
madx_script += "".join(
398431
f"call, file = '{self.model_dir / modifier}'; {MODIFIER_TAG}\n"
399432
for modifier in self.modifiers
400433
)
434+
435+
if self.year in ['2012', '2015', '2016', '2017', '2018', '2021', 'hllhc1.3']:
436+
# backwards compatibility with pre acc-models optics
437+
madx_script += (
438+
f"\n! ----- Defining Configuration Specifics -----\n"
439+
f"xing_angles = {'1' if self.xing else '0'};\n"
440+
f"if(xing_angles==1){{\n"
441+
f" exec, set_crossing_scheme_ON();\n"
442+
f"}}else{{\n"
443+
f" exec, set_default_crossing_scheme();\n"
444+
f"}}\n"
445+
)
446+
else:
447+
madx_script += 'call, file="knobs.madx";\n\n'
448+
401449
madx_script += (
402-
f"\n! ----- Defining Configuration Specifics -----\n"
403-
f"exec, cycle_sequences();\n"
404-
f"xing_angles = {'1' if self.xing else '0'};\n"
405-
f"if(xing_angles==1){{\n"
406-
f" exec, set_crossing_scheme_ON();\n"
407-
f"}}else{{\n"
408-
f" exec, set_default_crossing_scheme();\n"
409-
f"}}\n"
450+
"exec, cycle_sequences();\n"
410451
f"use, sequence = LHCB{self.beam};\n"
411452
f"option, echo;\n"
412453
)
@@ -422,14 +463,19 @@ def get_base_madx_script(self, best_knowledge: bool = False) -> str:
422463
madx_script += "exec, high_beta_matcher();\n"
423464

424465
madx_script += f"\n! ----- Matching Knobs and Output Files -----\n"
425-
if self._uses_ats_knobs():
426-
LOGGER.debug("According to the optics year or the --ats flag being provided, ATS macros and knobs will be used")
427-
madx_script += f"exec, match_tunes_ats({self.nat_tunes[0]}, {self.nat_tunes[1]}, {self.beam});\n"
428-
madx_script += f"exec, coupling_knob_ats({self.beam});\n"
429-
else:
430-
madx_script += f"exec, match_tunes({self.nat_tunes[0]}, {self.nat_tunes[1]}, {self.beam});\n"
431-
madx_script += f"exec, coupling_knob({self.beam});\n"
432-
466+
467+
# in the best knowledge case, all knobs are loaded from actual knowledge
468+
if not best_knowledge:
469+
if self._uses_ats_knobs():
470+
LOGGER.debug(
471+
"According to the optics year or the --ats flag being provided, ATS macros and knobs will be used"
472+
)
473+
madx_script += f"exec, match_tunes_ats({self.nat_tunes[0]}, {self.nat_tunes[1]}, {self.beam});\n"
474+
madx_script += f"exec, coupling_knob_ats({self.beam});\n"
475+
else:
476+
madx_script += f"exec, match_tunes({self.nat_tunes[0]}, {self.nat_tunes[1]}, {self.beam});\n"
477+
madx_script += f"exec, coupling_knob({self.beam});\n"
478+
433479
if ats_md:
434480
madx_script += "exec, full_response_ats();\n"
435481

@@ -461,13 +507,16 @@ def _uses_run3_macros(self) -> bool:
461507
except ValueError: # if a "hllhc1.x" year is given
462508
return False
463509

510+
464511
# General functions ##########################################################
465512

466513

467514
def _get_call_main_for_year(year: str) -> str:
468515
call_main = f"call, file = '{_get_file_for_year(year, 'main.seq')}';\n"
469516
if year == "2012":
470-
call_main += f"call, file = '{LHC_DIR / '2012' / 'install_additional_elements.madx'}';\n"
517+
call_main += (
518+
f"call, file = '{LHC_DIR / '2012' / 'install_additional_elements.madx'}';\n"
519+
)
471520
if year == "hllhc1.3":
472521
call_main += f"call, file = '{LHC_DIR / 'hllhc1.3' / 'main_update.seq'}';\n"
473522
return call_main

0 commit comments

Comments
 (0)