6161
6262
6363"""
64+
6465from __future__ import annotations
6566
6667####### WORKAROUND FOR JAVA ISSUES WITH LHCOP ##################################
9798from omc3 .utils .mock import cern_network_import
9899
99100if TYPE_CHECKING :
100- from collections .abc import Sequence
101+ from collections .abc import Sequence
101102
102103pytimber = cern_network_import ("pytimber" )
103104
104105LOGGER = 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!
107110ACC_MODELS_LHC = Path ("acc-models-lhc" )
108111KNOBS_FILE_ACC_MODELS = ACC_MODELS_LHC / "operation" / "knobs.txt"
109112KNOBS_FILE_AFS = AFS_ACC_MODELS_LHC / "operation" / "knobs.txt"
110113
111114MINUS_CHARS : tuple [str , ...] = ("_" , "-" )
112115STATE_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
121124class 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
129133class 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)
296292def 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+
318317def 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+
362364def 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
407411def 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
462470def _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+
493502def _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
511524def 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
542557def _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
561576def _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
571586def 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+
582600def _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
590608def _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
613631def _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+
630654def lsa2name (lsa_name : str ) -> str :
631655 """LSA name -> Variable in Timber/StateTracker conversion."""
632656 return lsa_name .replace ("/" , ":" )
0 commit comments