Skip to content

Commit ec2198f

Browse files
committed
Ruff the knob extractor and add to git blame
1 parent 81738e0 commit ec2198f

File tree

2 files changed

+86
-61
lines changed

2 files changed

+86
-61
lines changed

.git-blame-ignore-revs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55
820a51e3c67c8675880b9c949ac09f4db8bd8e9f
66
4e59cd0f7eb6a4279dc3f5cfbaed2b03b1918df3
77
0c19f7f7aa2e69a46c0e72fb99d4c3a29a1726c1
8+
81738e03

omc3/knob_extractor.py

Lines changed: 85 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
6262
6363
"""
64+
6465
from __future__ import annotations
6566

6667
####### WORKAROUND FOR JAVA ISSUES WITH LHCOP ##################################
@@ -97,37 +98,41 @@
9798
from omc3.utils.mock import cern_network_import
9899

99100
if TYPE_CHECKING:
100-
from collections.abc import Sequence
101+
from collections.abc import Sequence
101102

102103
pytimber = cern_network_import("pytimber")
103104

104105
LOGGER = get_logger(__name__)
105106

106-
AFS_ACC_MODELS_LHC = Path("/afs/cern.ch/eng/acc-models/lhc/current") # make sure 'current' linked correctly!
107+
AFS_ACC_MODELS_LHC = Path(
108+
"/afs/cern.ch/eng/acc-models/lhc/current"
109+
) # make sure 'current' linked correctly!
107110
ACC_MODELS_LHC = Path("acc-models-lhc")
108111
KNOBS_FILE_ACC_MODELS = ACC_MODELS_LHC / "operation" / "knobs.txt"
109112
KNOBS_FILE_AFS = AFS_ACC_MODELS_LHC / "operation" / "knobs.txt"
110113

111114
MINUS_CHARS: tuple[str, ...] = ("_", "-")
112115
STATE_VARIABLES: dict[str, str] = {
113-
'opticName': 'Optics',
114-
'beamProcess': 'Beam Process',
115-
'opticId': 'Optics ID',
116-
'hyperCycle': 'HyperCycle',
116+
"opticName": "Optics",
117+
"beamProcess": "Beam Process",
118+
"opticId": "Optics ID",
119+
"hyperCycle": "HyperCycle",
117120
# 'secondsInBeamProcess ': 'Beam Process running (s)',
118121
}
119122

120123

121124
class Col:
122-
""" DataFrame Columns used in this script. """
125+
"""DataFrame Columns used in this script."""
126+
123127
madx: str = "madx"
124128
lsa: str = "lsa"
125129
scaling: str = "scaling"
126130
value: str = "value"
127131

128132

129133
class Head:
130-
""" TFS Headers used in this script."""
134+
"""TFS Headers used in this script."""
135+
131136
time: str = "EXTRACTION_TIME"
132137

133138

@@ -179,26 +184,20 @@ class Head:
179184
"LHCBEAM1:IP-SDISP-QPBUMP",
180185
"LHCBEAM2:IP-SDISP-QPBUMP",
181186
],
182-
"mo": [
183-
"LHCBEAM1:LANDAU_DAMPING",
184-
"LHCBEAM2:LANDAU_DAMPING"
185-
],
187+
"mo": ["LHCBEAM1:LANDAU_DAMPING", "LHCBEAM2:LANDAU_DAMPING"],
186188
"lumi_scan": [
187189
"LHCBEAM1:IP1_SEPSCAN_X_MM",
188190
"LHCBEAM1:IP1_SEPSCAN_Y_MM",
189191
"LHCBEAM2:IP1_SEPSCAN_X_MM",
190192
"LHCBEAM2:IP1_SEPSCAN_Y_MM",
191-
192193
"LHCBEAM1:IP2_SEPSCAN_X_MM",
193194
"LHCBEAM1:IP2_SEPSCAN_Y_MM",
194195
"LHCBEAM2:IP2_SEPSCAN_X_MM",
195196
"LHCBEAM2:IP2_SEPSCAN_Y_MM",
196-
197197
"LHCBEAM1:IP5_SEPSCAN_X_MM",
198198
"LHCBEAM1:IP5_SEPSCAN_Y_MM",
199199
"LHCBEAM2:IP5_SEPSCAN_X_MM",
200200
"LHCBEAM2:IP5_SEPSCAN_Y_MM",
201-
202201
"LHCBEAM1:IP8_SEPSCAN_X_MM",
203202
"LHCBEAM1:IP8_SEPSCAN_Y_MM",
204203
"LHCBEAM2:IP8_SEPSCAN_X_MM",
@@ -230,7 +229,7 @@ def get_params():
230229
return EntryPointParameters(
231230
knobs={
232231
"type": str,
233-
"nargs": '*',
232+
"nargs": "*",
234233
"help": (
235234
"A list of knob names or categories to extract. "
236235
f"Available categories are: {', '.join(KNOB_CATEGORIES.keys())}."
@@ -261,17 +260,13 @@ def get_params():
261260
),
262261
},
263262
state={
264-
"action": 'store_true',
265-
"help": (
266-
"Prints the state of the StateTracker. "
267-
"Does not extract anything else."
268-
),
263+
"action": "store_true",
264+
"help": ("Prints the state of the StateTracker. Does not extract anything else."),
269265
},
270266
output={
271267
"type": PathOrStr,
272268
"help": (
273-
"Specify user-defined output path. "
274-
"This should probably be `model_dir/knobs.madx`"
269+
"Specify user-defined output path. This should probably be `model_dir/knobs.madx`"
275270
),
276271
},
277272
knob_definitions={
@@ -286,17 +281,20 @@ def get_params():
286281

287282

288283
@entrypoint(
289-
get_params(), strict=True,
284+
get_params(),
285+
strict=True,
290286
argument_parser_args={
291287
"epilog": USAGE_EXAMPLES,
292288
"formatter_class": argparse.RawDescriptionHelpFormatter,
293-
"prog": "Knob Extraction Tool."
294-
}
289+
"prog": "Knob Extraction Tool.",
290+
},
295291
)
296292
def main(opt) -> tfs.TfsDataFrame:
297-
""" Main knob extracting function. """
298-
ldb = pytimber.LoggingDB(source="nxcals", loglevel=logging.ERROR,
299-
sparkprops={"spark.ui.showConsoleProgress": "false"}
293+
"""Main knob extracting function."""
294+
ldb = pytimber.LoggingDB(
295+
source="nxcals",
296+
loglevel=logging.ERROR,
297+
sparkprops={"spark.ui.showConsoleProgress": "false"},
300298
)
301299
time = _parse_time(opt.time, opt.timedelta)
302300

@@ -315,6 +313,7 @@ def main(opt) -> tfs.TfsDataFrame:
315313

316314
# State Extraction -------------------------------------------------------------
317315

316+
318317
def get_state(ldb, time: datetime) -> dict[str, str]:
319318
"""
320319
Standalone function to gather and log state data from the StateTracker.
@@ -348,9 +347,11 @@ def _get_state_as_df(state_dict: dict[str, str], time: datetime) -> tfs.TfsDataF
348347
Returns:
349348
tfs.DataFrame: States packed into dataframe with readable index.
350349
"""
351-
state_df = tfs.TfsDataFrame(index=list(STATE_VARIABLES.values()),
352-
columns=[Col.value, Col.lsa],
353-
headers={Head.time: time})
350+
state_df = tfs.TfsDataFrame(
351+
index=list(STATE_VARIABLES.values()),
352+
columns=[Col.value, Col.lsa],
353+
headers={Head.time: time},
354+
)
354355
for name, value in state_dict.items():
355356
state_df.loc[STATE_VARIABLES[name], Col.lsa] = name
356357
state_df.loc[STATE_VARIABLES[name], Col.value] = value
@@ -359,6 +360,7 @@ def _get_state_as_df(state_dict: dict[str, str], time: datetime) -> tfs.TfsDataF
359360

360361
# Knobs Extraction -------------------------------------------------------------
361362

363+
362364
def extract(ldb, knobs: Sequence[str], time: datetime) -> dict[str, float]:
363365
"""
364366
Standalone function to gather data from the StateTracker.
@@ -383,7 +385,9 @@ def extract(ldb, knobs: Sequence[str], time: datetime) -> dict[str, float]:
383385
knobkey = f"LhcStateTracker:{knob}:target"
384386
knobs_extracted[knob] = None # to log that this was tried to be extracted.
385387

386-
knobvalue = ldb.get(knobkey, time.timestamp()) # use timestamp to preserve timezone info
388+
knobvalue = ldb.get(
389+
knobkey, time.timestamp()
390+
) # use timestamp to preserve timezone info
387391
if knobkey not in knobvalue:
388392
LOGGER.debug(f"{knob} not found in StateTracker")
389393
continue
@@ -405,7 +409,7 @@ def extract(ldb, knobs: Sequence[str], time: datetime) -> dict[str, float]:
405409

406410

407411
def check_for_undefined_knobs(knobs_definitions: pd.DataFrame, knob_categories: Sequence[str]):
408-
""" Check that all knobs are actually defined in the knobs-definitions.
412+
"""Check that all knobs are actually defined in the knobs-definitions.
409413
410414
411415
Args:
@@ -416,7 +420,9 @@ def check_for_undefined_knobs(knobs_definitions: pd.DataFrame, knob_categories:
416420
KeyError: If one or more of the knobs don't have a definition.
417421
418422
"""
419-
knob_names = [knob for category in knob_categories for knob in KNOB_CATEGORIES.get(category, [category])]
423+
knob_names = [
424+
knob for category in knob_categories for knob in KNOB_CATEGORIES.get(category, [category])
425+
]
420426
undefined_knobs = [knob for knob in knob_names if knob not in knobs_definitions.index]
421427
if undefined_knobs:
422428
raise KeyError(
@@ -425,9 +431,9 @@ def check_for_undefined_knobs(knobs_definitions: pd.DataFrame, knob_categories:
425431
)
426432

427433

428-
def _extract_and_gather(ldb, knobs_definitions: pd.DataFrame,
429-
knob_categories: Sequence[str],
430-
time: datetime) -> tfs.TfsDataFrame:
434+
def _extract_and_gather(
435+
ldb, knobs_definitions: pd.DataFrame, knob_categories: Sequence[str], time: datetime
436+
) -> tfs.TfsDataFrame:
431437
"""
432438
Main function to gather data from the StateTracker and the knob-definitions.
433439
All given knobs (either in categories or as knob names) to be extracted
@@ -451,16 +457,18 @@ def _extract_and_gather(ldb, knobs_definitions: pd.DataFrame,
451457
extracted_knobs = extract(ldb, knobs=knob_categories, time=time)
452458

453459
knob_names = list(extracted_knobs.keys())
454-
knobs = tfs.TfsDataFrame(index=knob_names,
455-
columns=[Col.lsa, Col.madx, Col.scaling, Col.value],
456-
headers={Head.time: time})
460+
knobs = tfs.TfsDataFrame(
461+
index=knob_names,
462+
columns=[Col.lsa, Col.madx, Col.scaling, Col.value],
463+
headers={Head.time: time},
464+
)
457465
knobs[[Col.lsa, Col.madx, Col.scaling]] = knobs_definitions.loc[knob_names, :]
458466
knobs[Col.value] = pd.Series(extracted_knobs)
459467
return knobs
460468

461469

462470
def _write_knobsfile(output: Path | str, collected_knobs: tfs.TfsDataFrame):
463-
""" Takes the collected knobs and writes them out into a text-file. """
471+
"""Takes the collected knobs and writes them out into a text-file."""
464472
collected_knobs = collected_knobs.copy() # to not modify the df
465473

466474
# Sort the knobs by category
@@ -490,26 +498,31 @@ def _write_knobsfile(output: Path | str, collected_knobs: tfs.TfsDataFrame):
490498

491499
# Knobs Definitions ------------------------------------------------------------
492500

501+
493502
def _get_knobs_def_file(user_defined: Path | str | None = None) -> Path:
494-
""" Check which knobs-definition file is appropriate to take. """
503+
"""Check which knobs-definition file is appropriate to take."""
495504
if user_defined is not None:
496505
LOGGER.info(f"Using user knobs-definition file: '{user_defined}")
497506
return Path(user_defined)
498507

499508
if KNOBS_FILE_ACC_MODELS.is_file():
500-
LOGGER.info(f"Using given acc-models folder's knobs.txt as knobsdefinition file: '{KNOBS_FILE_ACC_MODELS}")
509+
LOGGER.info(
510+
f"Using given acc-models folder's knobs.txt as knobsdefinition file: '{KNOBS_FILE_ACC_MODELS}"
511+
)
501512
return KNOBS_FILE_ACC_MODELS
502513

503514
if KNOBS_FILE_AFS.is_file():
504515
# if all fails, fall back to lhc acc-models
505-
LOGGER.info(f"Using afs-fallback acc-models folder's knobs.txt as knobs-definition file: '{KNOBS_FILE_AFS}'")
516+
LOGGER.info(
517+
f"Using afs-fallback acc-models folder's knobs.txt as knobs-definition file: '{KNOBS_FILE_AFS}'"
518+
)
506519
return KNOBS_FILE_AFS
507520

508521
raise FileNotFoundError("None of the knobs-definition files are available.")
509522

510523

511524
def load_knobs_definitions(file_path: Path | str) -> pd.DataFrame:
512-
""" Load the knobs-definition file and convert into a DataFrame.
525+
"""Load the knobs-definition file and convert into a DataFrame.
513526
Each line in this file should consist of at least three comma separated
514527
entries in the following order: madx-name, lsa-name, scaling factor.
515528
Other columns are ignored.
@@ -530,17 +543,19 @@ def load_knobs_definitions(file_path: Path | str) -> pd.DataFrame:
530543
converters = {Col.madx: str.strip, Col.lsa: str.strip} # strip whitespaces
531544
dtypes = {Col.scaling: float}
532545
names = (Col.madx, Col.lsa, Col.scaling)
533-
df = pd.read_csv(file_path,
534-
comment="#",
535-
usecols=list(range(len(names))), # only read the first columns
536-
names=names,
537-
dtype=dtypes,
538-
converters=converters)
546+
df = pd.read_csv(
547+
file_path,
548+
comment="#",
549+
usecols=list(range(len(names))), # only read the first columns
550+
names=names,
551+
dtype=dtypes,
552+
converters=converters,
553+
)
539554
return _to_knobs_dataframe(df)
540555

541556

542557
def _to_knobs_dataframe(df: pd.DataFrame) -> pd.DataFrame:
543-
""" Adapts a DataFrame to the conventions used here:
558+
"""Adapts a DataFrame to the conventions used here:
544559
StateTracker variable name as index, all columns lower-case.
545560
546561
Args:
@@ -559,7 +574,7 @@ def _to_knobs_dataframe(df: pd.DataFrame) -> pd.DataFrame:
559574

560575

561576
def _parse_knobs_defintions(knobs_def_input: Path | str | pd.DataFrame | None) -> pd.DataFrame:
562-
""" Parse the given knob-definitions either from a csv-file or from a DataFrame. """
577+
"""Parse the given knob-definitions either from a csv-file or from a DataFrame."""
563578
if isinstance(knobs_def_input, pd.DataFrame):
564579
return _to_knobs_dataframe(knobs_def_input)
565580

@@ -570,25 +585,28 @@ def _parse_knobs_defintions(knobs_def_input: Path | str | pd.DataFrame | None) -
570585

571586
def get_madx_command(knob_data: pd.Series) -> str:
572587
if Col.value not in knob_data.index:
573-
raise KeyError("Value entry not found in extracted knob_data. "
574-
"Something went wrong as it should at least be NaN.")
588+
raise KeyError(
589+
"Value entry not found in extracted knob_data. "
590+
"Something went wrong as it should at least be NaN."
591+
)
575592
if knob_data[Col.value] is None or pd.isna(knob_data[Col.value]):
576593
return f"! {knob_data[Col.madx]} : No Value extracted"
577594
return f"{knob_data[Col.madx]} := {knob_data[Col.value] * knob_data[Col.scaling]};"
578595

579596

580597
# Time Tools -------------------------------------------------------------------
581598

599+
582600
def _parse_time(time: str, timedelta: str = None) -> datetime:
583-
""" Parse time from given time-input. """
601+
"""Parse time from given time-input."""
584602
t = _parse_time_from_str(time)
585603
if timedelta:
586604
t = _add_time_delta(t, timedelta)
587605
return t
588606

589607

590608
def _parse_time_from_str(time_str: str) -> datetime:
591-
""" Parse time from given string. """
609+
"""Parse time from given string."""
592610
# Now? ---
593611
if time_str.lower() == "now":
594612
return datetime.now(timezone.utc)
@@ -611,14 +629,19 @@ def _parse_time_from_str(time_str: str) -> datetime:
611629

612630

613631
def _add_time_delta(time: datetime, delta_str: str) -> datetime:
614-
""" Parse delta-string and add time-delta to time. """
632+
"""Parse delta-string and add time-delta to time."""
615633
sign = -1 if delta_str[0] in MINUS_CHARS else 1
616634
all_deltas = re.findall(r"(\d+)(\w)", delta_str) # tuples (value, timeunit-char)
617635
# mapping char to the time-unit as accepted by relativedelta,
618636
# following ISO-8601 for time durations
619637
char2unit = {
620-
"s": 'seconds', "m": 'minutes', "h": 'hours',
621-
"d": 'days', "w": 'weeks', "M": 'months', "Y": "years",
638+
"s": "seconds",
639+
"m": "minutes",
640+
"h": "hours",
641+
"d": "days",
642+
"w": "weeks",
643+
"M": "months",
644+
"Y": "years",
622645
}
623646
# add all deltas, which are tuples of (value, timeunit-char)
624647
time_parts = {char2unit[delta[1]]: sign * int(delta[0]) for delta in all_deltas}
@@ -627,6 +650,7 @@ def _add_time_delta(time: datetime, delta_str: str) -> datetime:
627650

628651
# Other tools ------------------------------------------------------------------
629652

653+
630654
def lsa2name(lsa_name: str) -> str:
631655
"""LSA name -> Variable in Timber/StateTracker conversion."""
632656
return lsa_name.replace("/", ":")

0 commit comments

Comments
 (0)