lstosa 0.10.18__py3-none-any.whl → 0.10.19__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. {lstosa-0.10.18.dist-info → lstosa-0.10.19.dist-info}/METADATA +2 -2
  2. {lstosa-0.10.18.dist-info → lstosa-0.10.19.dist-info}/RECORD +41 -40
  3. {lstosa-0.10.18.dist-info → lstosa-0.10.19.dist-info}/WHEEL +1 -1
  4. {lstosa-0.10.18.dist-info → lstosa-0.10.19.dist-info}/entry_points.txt +1 -0
  5. osa/_version.py +9 -4
  6. osa/configs/options.py +2 -0
  7. osa/configs/sequencer.cfg +12 -4
  8. osa/conftest.py +127 -3
  9. osa/high_level/significance.py +3 -3
  10. osa/high_level/tests/test_significance.py +3 -0
  11. osa/job.py +48 -25
  12. osa/nightsummary/extract.py +11 -2
  13. osa/nightsummary/tests/test_extract.py +3 -0
  14. osa/paths.py +102 -23
  15. osa/provenance/capture.py +1 -1
  16. osa/provenance/config/definition.yaml +7 -0
  17. osa/provenance/utils.py +22 -7
  18. osa/scripts/autocloser.py +0 -10
  19. osa/scripts/calibration_pipeline.py +4 -0
  20. osa/scripts/closer.py +132 -53
  21. osa/scripts/copy_datacheck.py +5 -3
  22. osa/scripts/datasequence.py +45 -71
  23. osa/scripts/provprocess.py +16 -7
  24. osa/scripts/sequencer.py +34 -26
  25. osa/scripts/sequencer_catB_tailcuts.py +223 -0
  26. osa/scripts/sequencer_webmaker.py +4 -0
  27. osa/scripts/simulate_processing.py +4 -7
  28. osa/scripts/tests/test_osa_scripts.py +64 -20
  29. osa/scripts/update_source_catalog.py +5 -2
  30. osa/tests/test_jobs.py +28 -11
  31. osa/tests/test_paths.py +6 -6
  32. osa/utils/cliopts.py +37 -32
  33. osa/utils/register.py +18 -13
  34. osa/utils/tests/test_utils.py +14 -0
  35. osa/utils/utils.py +173 -56
  36. osa/workflow/dl3.py +1 -2
  37. osa/workflow/stages.py +16 -11
  38. osa/workflow/tests/test_dl3.py +2 -1
  39. osa/workflow/tests/test_stages.py +7 -4
  40. {lstosa-0.10.18.dist-info → lstosa-0.10.19.dist-info}/LICENSE +0 -0
  41. {lstosa-0.10.18.dist-info → lstosa-0.10.19.dist-info}/top_level.txt +0 -0
@@ -9,11 +9,13 @@ from osa.configs.config import cfg
9
9
  from osa.job import historylevel
10
10
  from osa.workflow.stages import AnalysisStage
11
11
  from osa.provenance.capture import trace
12
+ from osa.paths import get_catB_calibration_filename
12
13
  from osa.utils.cliopts import data_sequence_cli_parsing
13
14
  from osa.utils.logging import myLogger
14
15
  from osa.utils.utils import date_to_dir
16
+ from osa.paths import catB_closed_file_exists
15
17
 
16
- __all__ = ["data_sequence", "r0_to_dl1", "dl1_to_dl2", "dl1ab", "dl1_datacheck"]
18
+ __all__ = ["data_sequence", "r0_to_dl1", "dl1ab", "dl1_datacheck"]
17
19
 
18
20
  log = myLogger(logging.getLogger())
19
21
 
@@ -27,6 +29,8 @@ def data_sequence(
27
29
  run_summary: Path,
28
30
  pedestal_ids_file: Path,
29
31
  run_str: str,
32
+ dl1b_config: Path,
33
+ dl1_prod_id: str,
30
34
  ):
31
35
  """
32
36
  Performs all the steps to process a whole run.
@@ -50,10 +54,10 @@ def data_sequence(
50
54
  history_file = Path(options.directory) / f"sequence_{options.tel_id}_{run_str}.history"
51
55
  # Set the starting level and corresponding return code from last analysis step
52
56
  # registered in the history file.
53
- level, rc = (4, 0) if options.simulate else historylevel(history_file, "DATA")
57
+ level, rc = (3, 0) if options.simulate else historylevel(history_file, "DATA")
54
58
  log.info(f"Going to level {level}")
55
59
 
56
- if level == 4:
60
+ if level == 3:
57
61
  rc = r0_to_dl1(
58
62
  calibration_file,
59
63
  pedestal_file,
@@ -67,32 +71,23 @@ def data_sequence(
67
71
  level -= 1
68
72
  log.info(f"Going to level {level}")
69
73
 
70
- if level == 3:
71
- rc = dl1ab(run_str)
72
- if cfg.getboolean("lstchain", "store_image_dl1ab"):
73
- level -= 1
74
- log.info(f"Going to level {level}")
75
- else:
76
- level -= 2
77
- log.info(f"No images stored in dl1ab. Producing DL2. Going to level {level}")
78
-
79
74
  if level == 2:
80
- rc = dl1_datacheck(run_str)
81
- if options.no_dl2:
75
+ if options.no_dl1ab:
82
76
  level = 0
83
- log.info(f"No DL2 are going to be produced. Going to level {level}")
77
+ log.info(f"No DL1B are going to be produced. Going to level {level}")
84
78
  else:
85
- level -= 1
86
- log.info(f"Going to level {level}")
79
+ rc = dl1ab(run_str, dl1b_config, dl1_prod_id)
80
+ if cfg.getboolean("lstchain", "store_image_dl1ab"):
81
+ level -= 1
82
+ log.info(f"Going to level {level}")
83
+ else:
84
+ level -= 2
85
+ log.info(f"No images stored in dl1ab. Going to level {level}")
87
86
 
88
87
  if level == 1:
89
- if options.no_dl2:
90
- level = 0
91
- log.info(f"No DL2 are going to be produced. Going to level {level}")
92
- else:
93
- rc = dl1_to_dl2(run_str)
94
- level -= 1
95
- log.info(f"Going to level {level}")
88
+ rc = dl1_datacheck(run_str, dl1_prod_id)
89
+ level -= 1
90
+ log.info(f"Going to level {level}")
96
91
 
97
92
  if level == 0:
98
93
  log.info(f"Job for sequence {run_str} finished without fatal errors")
@@ -166,7 +161,7 @@ def r0_to_dl1(
166
161
 
167
162
 
168
163
  @trace
169
- def dl1ab(run_str: str) -> int:
164
+ def dl1ab(run_str: str, dl1b_config: Path, dl1_prod_id: str) -> int:
170
165
  """
171
166
  Prepare and launch the actual lstchain script that is performing
172
167
  the image cleaning considering the interleaved pedestal information
@@ -181,26 +176,39 @@ def dl1ab(run_str: str) -> int:
181
176
  rc: int
182
177
  Return code of the executed command.
183
178
  """
179
+
180
+ # Prepare and launch the actual lstchain script
181
+ command = cfg.get("lstchain", "dl1ab")
182
+
184
183
  # Create a new subdirectory for the dl1ab output
185
- dl1ab_subdirectory = Path(options.directory) / options.dl1_prod_id
184
+ dl1ab_subdirectory = Path(options.directory) / dl1_prod_id
186
185
  dl1ab_subdirectory.mkdir(parents=True, exist_ok=True)
187
- dl1b_config = Path(cfg.get("lstchain", "dl1b_config"))
188
186
  # DL1a input file from base running_analysis directory
189
187
  input_dl1_datafile = Path(options.directory) / f"dl1_LST-1.Run{run_str}.h5"
190
188
  # DL1b output file to be stored in the dl1ab subdirectory
191
189
  output_dl1_datafile = dl1ab_subdirectory / f"dl1_LST-1.Run{run_str}.h5"
192
190
 
193
- # Prepare and launch the actual lstchain script
194
- command = cfg.get("lstchain", "dl1ab")
195
191
  cmd = [
196
192
  command,
197
193
  f"--input-file={input_dl1_datafile}",
198
194
  f"--output-file={output_dl1_datafile}",
199
195
  f"--config={dl1b_config}",
200
196
  ]
197
+
201
198
  if not cfg.getboolean("lstchain", "store_image_dl1ab"):
202
199
  cmd.append("--no-image=True")
203
200
 
201
+ if cfg.getboolean("lstchain", "apply_catB_calibration"):
202
+ if catB_closed_file_exists(int(run_str[:5])):
203
+ catB_calibration_file = get_catB_calibration_filename(int(run_str[:5]))
204
+ cmd.append(f"--catB-calibration-file={catB_calibration_file}")
205
+ else:
206
+ log.info(
207
+ f"Cat-B calibration did not finish yet for run {run_str[:5]}. "
208
+ "Please try again later."
209
+ )
210
+ sys.exit(1)
211
+
204
212
  if options.simulate:
205
213
  return 0
206
214
 
@@ -210,7 +218,7 @@ def dl1ab(run_str: str) -> int:
210
218
 
211
219
 
212
220
  @trace
213
- def dl1_datacheck(run_str: str) -> int:
221
+ def dl1_datacheck(run_str: str, dl1_prod_id: str) -> int:
214
222
  """
215
223
  Run datacheck script
216
224
 
@@ -223,9 +231,9 @@ def dl1_datacheck(run_str: str) -> int:
223
231
  rc: int
224
232
  """
225
233
  # Create a new subdirectory for the dl1ab output
226
- dl1ab_subdirectory = Path(options.directory) / options.dl1_prod_id
234
+ dl1ab_subdirectory = Path(options.directory) / dl1_prod_id
227
235
  input_dl1_datafile = dl1ab_subdirectory / f"dl1_LST-1.Run{run_str}.h5"
228
- output_directory = Path(options.directory) / options.dl1_prod_id
236
+ output_directory = Path(options.directory) / dl1_prod_id
229
237
  output_directory.mkdir(parents=True, exist_ok=True)
230
238
 
231
239
  # Prepare and launch the actual lstchain script
@@ -247,46 +255,8 @@ def dl1_datacheck(run_str: str) -> int:
247
255
  return analysis_step.rc
248
256
 
249
257
 
250
- @trace
251
- def dl1_to_dl2(run_str: str) -> int:
252
- """
253
- It prepares and execute the dl1 to dl2 lstchain scripts that applies
254
- the already trained RFs models to DL1 files. It identifies the
255
- primary particle, reconstructs its energy and direction.
256
-
257
- Parameters
258
- ----------
259
- run_str: str
260
-
261
- Returns
262
- -------
263
- rc: int
264
- """
265
- dl1ab_subdirectory = Path(options.directory) / options.dl1_prod_id
266
- dl2_subdirectory = Path(options.directory) / options.dl2_prod_id
267
- dl2_config = Path(cfg.get("lstchain", "dl2_config"))
268
- rf_models_directory = Path(cfg.get("lstchain", "RF_MODELS"))
269
- dl1_file = dl1ab_subdirectory / f"dl1_LST-1.Run{run_str}.h5"
270
-
271
- command = cfg.get("lstchain", "dl1_to_dl2")
272
- cmd = [
273
- command,
274
- f"--input-file={dl1_file}",
275
- f"--output-dir={dl2_subdirectory}",
276
- f"--path-models={rf_models_directory}",
277
- f"--config={dl2_config}",
278
- ]
279
-
280
- if options.simulate:
281
- return 0
282
-
283
- analysis_step = AnalysisStage(run=run_str, command_args=cmd, config_file=dl2_config.name)
284
- analysis_step.execute()
285
- return analysis_step.rc
286
-
287
-
288
258
  def main():
289
- """Performs the analysis steps to convert raw data into DL2 files."""
259
+ """Performs the analysis steps to convert raw data into DL1b files."""
290
260
  (
291
261
  calibration_file,
292
262
  drs4_ped_file,
@@ -296,6 +266,8 @@ def main():
296
266
  run_summary_file,
297
267
  pedestal_ids_file,
298
268
  run_number,
269
+ dl1b_config,
270
+ dl1_prod_id,
299
271
  ) = data_sequence_cli_parsing()
300
272
 
301
273
  if options.verbose:
@@ -313,6 +285,8 @@ def main():
313
285
  run_summary_file,
314
286
  pedestal_ids_file,
315
287
  run_number,
288
+ dl1b_config,
289
+ dl1_prod_id,
316
290
  )
317
291
  sys.exit(rc)
318
292
 
@@ -17,6 +17,8 @@ from osa.provenance.io import provdoc2graph, provdoc2json, provlist2provdoc, rea
17
17
  from osa.provenance.utils import get_log_config
18
18
  from osa.utils.cliopts import provprocessparsing
19
19
  from osa.utils.logging import myLogger
20
+ from osa.utils.utils import date_to_dir
21
+ from osa.paths import get_dl1_prod_id_and_config, get_dl2_prod_id
20
22
 
21
23
  __all__ = ["copy_used_file", "parse_lines_log", "parse_lines_run", "produce_provenance"]
22
24
 
@@ -110,7 +112,8 @@ def parse_lines_log(filter_cut, calib_runs, run_number):
110
112
  keep = True
111
113
  # make session starts with calibration
112
114
  if session_id and filter_cut == "all" and not filtered:
113
- prov_dict["session_id"] = f"{options.date}{run_number}"
115
+ nightdir = date_to_dir(options.date)
116
+ prov_dict["session_id"] = f"{nightdir}{run_number}"
114
117
  prov_dict["name"] = run_number
115
118
  prov_dict["observation_run"] = run_number
116
119
  line = f"{ll[0]}{PROV_PREFIX}{ll[1]}{PROV_PREFIX}{prov_dict}\n"
@@ -336,7 +339,8 @@ def define_paths(grain, start_path, end_path, base_filename):
336
339
  paths = {}
337
340
 
338
341
  # check destination folder exists
339
- step_path = Path(start_path) / options.date / options.prod_id / end_path
342
+ nightdir = date_to_dir(options.date)
343
+ step_path = Path(start_path) / nightdir / options.prod_id / end_path
340
344
  if not step_path.exists():
341
345
  log.error(f"Path {step_path} does not exist")
342
346
 
@@ -381,8 +385,9 @@ def produce_provenance(session_log_filename, base_filename):
381
385
  """
382
386
 
383
387
  if options.filter == "calibration" or not options.filter:
388
+ dl1_prod_id = get_dl1_prod_id_and_config(int(options.run))[0]
384
389
  paths_calibration = define_paths(
385
- "calibration_to_dl1", PATH_DL1, options.dl1_prod_id, base_filename
390
+ "calibration_to_dl1", PATH_DL1, dl1_prod_id, base_filename
386
391
  )
387
392
  plines_drs4 = parse_lines_run(
388
393
  "drs4_pedestal",
@@ -402,7 +407,8 @@ def produce_provenance(session_log_filename, base_filename):
402
407
  pass
403
408
 
404
409
  if options.filter == "r0_to_dl1" or not options.filter:
405
- paths_r0_dl1 = define_paths("r0_to_dl1", PATH_DL1, options.dl1_prod_id, base_filename)
410
+ dl1_prod_id = get_dl1_prod_id_and_config(int(options.run))[0]
411
+ paths_r0_dl1 = define_paths("r0_to_dl1", PATH_DL1, dl1_prod_id, base_filename)
406
412
  plines_r0 = parse_lines_run(
407
413
  "r0_to_dl1",
408
414
  read_prov(filename=session_log_filename),
@@ -425,8 +431,9 @@ def produce_provenance(session_log_filename, base_filename):
425
431
  produce_provenance_files(plines_r0 + plines_ab[1:] + plines_check[1:], paths_r0_dl1)
426
432
 
427
433
  if options.filter == "dl1_to_dl2" or not options.filter:
434
+ dl2_prod_id = get_dl2_prod_id(int(options.run))
428
435
  if not options.no_dl2:
429
- paths_dl1_dl2 = define_paths("dl1_to_dl2", PATH_DL2, options.dl2_prod_id, base_filename)
436
+ paths_dl1_dl2 = define_paths("dl1_to_dl2", PATH_DL2, dl2_prod_id, base_filename)
430
437
  plines_dl2 = parse_lines_run(
431
438
  "dl1_to_dl2",
432
439
  read_prov(filename=session_log_filename),
@@ -441,16 +448,18 @@ def produce_provenance(session_log_filename, base_filename):
441
448
 
442
449
  # create calibration_to_dl1 and calibration_to_dl2 prov files
443
450
  if not options.filter:
451
+ dl1_prod_id = get_dl1_prod_id_and_config(int(options.run))[0]
444
452
  calibration_to_dl1 = define_paths(
445
- "calibration_to_dl1", PATH_DL1, options.dl1_prod_id, base_filename
453
+ "calibration_to_dl1", PATH_DL1, dl1_prod_id, base_filename
446
454
  )
447
455
  calibration_to_dl1_lines = calibration_lines + dl1_lines[1:]
448
456
  lines_dl1 = copy.deepcopy(calibration_to_dl1_lines)
449
457
  produce_provenance_files(lines_dl1, calibration_to_dl1)
450
458
 
451
459
  if not options.no_dl2:
460
+ dl2_prod_id = get_dl2_prod_id(int(options.run))
452
461
  calibration_to_dl2 = define_paths(
453
- "calibration_to_dl2", PATH_DL2, options.dl2_prod_id, base_filename
462
+ "calibration_to_dl2", PATH_DL2, dl2_prod_id, base_filename
454
463
  )
455
464
  calibration_to_dl2_lines = calibration_to_dl1_lines + dl1_dl2_lines[1:]
456
465
  lines_dl2 = copy.deepcopy(calibration_to_dl2_lines)
osa/scripts/sequencer.py CHANGED
@@ -22,10 +22,15 @@ from osa.job import (
22
22
  get_squeue_output,
23
23
  run_sacct,
24
24
  run_squeue,
25
+ are_all_jobs_correctly_finished,
26
+ )
27
+ from osa.nightsummary.extract import (
28
+ build_sequences,
29
+ extract_runs,
30
+ extract_sequences
25
31
  )
26
- from osa.nightsummary.extract import build_sequences
27
32
  from osa.nightsummary.nightsummary import run_summary_table
28
- from osa.paths import analysis_path
33
+ from osa.paths import analysis_path, destination_dir
29
34
  from osa.report import start
30
35
  from osa.utils.cliopts import sequencer_cli_parsing
31
36
  from osa.utils.logging import myLogger
@@ -115,7 +120,7 @@ def single_process(telescope):
115
120
  log.info(f"Sequencer is still running for date {date_to_iso(options.date)}. Try again later.")
116
121
  sys.exit(0)
117
122
 
118
- elif is_sequencer_completed(options.date) and not options.force_submit:
123
+ if is_sequencer_completed(options.date) and not options.force_submit:
119
124
  log.info(f"Sequencer already finished for date {date_to_iso(options.date)}. Exiting")
120
125
  sys.exit(0)
121
126
 
@@ -192,7 +197,7 @@ def update_sequence_status(seq_list):
192
197
  Decimal(get_status_for_sequence(seq, "DATACHECK") * 100) / seq.subruns
193
198
  )
194
199
  seq.muonstatus = int(Decimal(get_status_for_sequence(seq, "MUON") * 100) / seq.subruns)
195
- seq.dl2status = int(Decimal(get_status_for_sequence(seq, "DL2") * 100) / seq.subruns)
200
+ seq.dl2status = int(Decimal(get_status_for_sequence(seq, "DL2") * 100))
196
201
 
197
202
 
198
203
  def get_status_for_sequence(sequence, data_level) -> int:
@@ -210,17 +215,26 @@ def get_status_for_sequence(sequence, data_level) -> int:
210
215
  number_of_files : int
211
216
  """
212
217
  if data_level == "DL1AB":
213
- directory = options.directory / options.dl1_prod_id
214
- files = list(directory.glob(f"dl1_LST-1*{sequence.run}*.h5"))
215
-
218
+ try:
219
+ directory = options.directory / sequence.dl1_prod_id
220
+ files = list(directory.glob(f"dl1_LST-1*{sequence.run}*.h5"))
221
+ except AttributeError:
222
+ return 0
223
+
216
224
  elif data_level == "DL2":
217
- directory = options.directory / options.dl2_prod_id
218
- files = list(directory.glob(f"dl2_LST-1*{sequence.run}*.h5"))
219
-
225
+ try:
226
+ directory = destination_dir(concept="DL2", create_dir=False, dl2_prod_id=sequence.dl2_prod_id)
227
+ files = list(directory.glob(f"dl2_LST-1*{sequence.run}*.h5"))
228
+ except AttributeError:
229
+ return 0
230
+
220
231
  elif data_level == "DATACHECK":
221
- directory = options.directory / options.dl1_prod_id
222
- files = list(directory.glob(f"datacheck_dl1_LST-1*{sequence.run}*.h5"))
223
-
232
+ try:
233
+ directory = options.directory / sequence.dl1_prod_id
234
+ files = list(directory.glob(f"datacheck_dl1_LST-1*{sequence.run}*.h5"))
235
+ except AttributeError:
236
+ return 0
237
+
224
238
  else:
225
239
  prefix = cfg.get("PATTERN", f"{data_level}PREFIX")
226
240
  suffix = cfg.get("PATTERN", f"{data_level}SUFFIX")
@@ -340,20 +354,14 @@ def is_sequencer_completed(date: datetime.datetime) -> bool:
340
354
  """Check if the jobs launched by sequencer are already completed."""
341
355
  summary_table = run_summary_table(date)
342
356
  data_runs = summary_table[summary_table["run_type"] == "DATA"]
343
- sacct_output = run_sacct()
344
- sacct_info = get_sacct_output(sacct_output)
345
-
346
- for run in data_runs["run_id"]:
347
- jobs_run = sacct_info[sacct_info["JobName"]==f"LST1_{run:05d}"]
348
- if len(jobs_run["JobID"].unique())>1:
349
- last_job_id = sorted(jobs_run["JobID"].unique())[-1]
350
- jobs_run = sacct_info[sacct_info["JobID"]==last_job_id]
351
- incomplete_jobs = jobs_run[(jobs_run["State"] != "COMPLETED")]
352
- if len(jobs_run) == 0 or len(incomplete_jobs) != 0:
353
- return False
354
-
355
- return True
357
+ run_list = extract_runs(data_runs)
358
+ sequence_list = extract_sequences(options.date, run_list)
356
359
 
360
+ if are_all_jobs_correctly_finished(sequence_list):
361
+ return True
362
+ else:
363
+ log.info("Jobs did not correctly/yet finish")
364
+ return False
357
365
 
358
366
  def timeout_in_sequencer(date: datetime.datetime) -> bool:
359
367
  """Check if any of the jobs launched by sequencer finished in timeout."""
@@ -0,0 +1,223 @@
1
+ import glob
2
+ import re
3
+ import argparse
4
+ import logging
5
+ from pathlib import Path
6
+ from astropy.table import Table
7
+ import subprocess as sp
8
+
9
+ from osa.configs import options
10
+ from osa.configs.config import cfg
11
+ from osa.nightsummary.extract import get_last_pedcalib
12
+ from osa.utils.cliopts import valid_date, set_default_date_if_needed
13
+ from osa.utils.logging import myLogger
14
+ from osa.job import run_sacct, get_sacct_output
15
+ from osa.utils.utils import date_to_dir, get_calib_filters
16
+ from osa.paths import catB_closed_file_exists, catB_calibration_file_exists, analysis_path
17
+
18
+ log = myLogger(logging.getLogger())
19
+
20
+ parser = argparse.ArgumentParser()
21
+ parser.add_argument(
22
+ "-c",
23
+ "--config",
24
+ action="store",
25
+ type=Path,
26
+ help="Configuration file",
27
+ )
28
+ parser.add_argument(
29
+ "-d",
30
+ "--date",
31
+ type=valid_date,
32
+ default=None,
33
+ )
34
+ parser.add_argument(
35
+ "-v",
36
+ "--verbose",
37
+ action="store_true",
38
+ default=False,
39
+ help="Activate debugging mode.",
40
+ )
41
+ parser.add_argument(
42
+ "-s",
43
+ "--simulate",
44
+ action="store_true",
45
+ default=False,
46
+ help="Simulate launching of the sequencer_catB_tailcuts script.",
47
+ )
48
+ parser.add_argument(
49
+ "tel_id",
50
+ choices=["ST", "LST1", "LST2", "all"],
51
+ help="telescope identifier LST1, LST2, ST or all.",
52
+ )
53
+
54
+ def are_all_history_files_created(run_id: int) -> bool:
55
+ """Check if all the history files (one per subrun) were created for a given run."""
56
+ run_summary_dir = Path(cfg.get(options.tel_id, "RUN_SUMMARY_DIR"))
57
+ run_summary_file = run_summary_dir / f"RunSummary_{date_to_dir(options.date)}.ecsv"
58
+ run_summary = Table.read(run_summary_file)
59
+ n_subruns = run_summary[run_summary["run_id"] == run_id]["n_subruns"]
60
+ analysis_dir = Path(options.directory)
61
+ history_files = glob.glob(f"{str(analysis_dir)}/sequence_LST1_{run_id:05d}.????.history")
62
+ if len(history_files) == n_subruns:
63
+ return True
64
+ else:
65
+ return False
66
+
67
+
68
+ def r0_to_dl1_step_finished_for_run(run_id: int) -> bool:
69
+ """
70
+ Check if the step r0_to_dl1 finished successfully
71
+ for a given run by looking the history files.
72
+ """
73
+ if not are_all_history_files_created(run_id):
74
+ log.debug(f"All history files for run {run_id:05d} were not created yet.")
75
+ return False
76
+ analysis_dir = Path(options.directory)
77
+ history_files = glob.glob(f"{str(analysis_dir)}/sequence_LST1_{run_id:05d}.????.history")
78
+ for file in history_files:
79
+ rc = Path(file).read_text().splitlines()[-1][-1]
80
+ if rc != "0":
81
+ print(f"r0_to_dl1 step did not finish successfully (check file {file})")
82
+ return False
83
+ return True
84
+
85
+
86
+ def get_catB_last_job_id(run_id: int) -> int:
87
+ """Get job id of the last Cat-B calibration job that was launched for a given run."""
88
+ log_dir = Path(options.directory) / "log"
89
+ filenames = glob.glob(f"{log_dir}/catB_calibration_{run_id:05d}_*.err")
90
+ if filenames:
91
+ match = re.search(f"catB_calibration_{run_id:05d}_(\d+).err", sorted(filenames)[-1])
92
+ job_id = match.group(1)
93
+ return job_id
94
+
95
+
96
+ def launch_catB_calibration(run_id: int):
97
+ """
98
+ Launch the Cat-B calibration script for a given run if the Cat-B calibration
99
+ file has not been created yet. If the Cat-B calibration script was launched
100
+ before and it finished successfully, it creates a catB_{run}.closed file.
101
+ """
102
+ job_id = get_catB_last_job_id(run_id)
103
+ if job_id:
104
+ job_status = get_sacct_output(run_sacct(job_id=job_id))["State"]
105
+ if job_status.item() in ["RUNNING", "PENDING"]:
106
+ log.debug(f"Job {job_id} (corresponding to run {run_id:05d}) is still running.")
107
+
108
+ elif job_status.item() == "COMPLETED":
109
+ catB_closed_file = Path(options.directory) / f"catB_{run_id:05d}.closed"
110
+ catB_closed_file.touch()
111
+ log.debug(
112
+ f"Cat-B job {job_id} (corresponding to run {run_id:05d}) finished "
113
+ f"successfully. Creating file {catB_closed_file}"
114
+ )
115
+
116
+ else:
117
+ log.warning(f"Cat-B job {job_id} (corresponding to run {run_id:05d}) failed.")
118
+
119
+ else:
120
+ if catB_calibration_file_exists(run_id):
121
+ log.info(f"Cat-B calibration file already produced for run {run_id:05d}.")
122
+ return
123
+
124
+ command = cfg.get("lstchain", "catB_calibration")
125
+ options.filters = get_calib_filters(run_id)
126
+ base_dir = Path(cfg.get(options.tel_id, "BASE")).resolve()
127
+ r0_dir = Path(cfg.get(options.tel_id, "R0_DIR")).resolve()
128
+ interleaved_dir = Path(options.directory) / "interleaved"
129
+ log_dir = Path(options.directory) / "log"
130
+ catA_calib_run = get_last_pedcalib(options.date)
131
+ slurm_account = cfg.get("SLURM", "ACCOUNT")
132
+ cmd = ["sbatch", f"--account={slurm_account}", "--parsable",
133
+ "-o", f"{log_dir}/catB_calibration_{run_id:05d}_%j.out",
134
+ "-e", f"{log_dir}/catB_calibration_{run_id:05d}_%j.err",
135
+ command,
136
+ f"--run_number={run_id}",
137
+ f"--catA_calibration_run={catA_calib_run}",
138
+ f"--base_dir={base_dir}",
139
+ f"--r0-dir={r0_dir}",
140
+ f"--interleaved-dir={interleaved_dir}",
141
+ f"--filters={options.filters}",
142
+ ]
143
+ if not options.simulate:
144
+ job = sp.run(cmd, encoding="utf-8", capture_output=True, text=True, check=True)
145
+ job_id = job.stdout.strip()
146
+ log.debug(f"Launched Cat-B calibration job {job_id} for run {run_id}!")
147
+
148
+ else:
149
+ log.info(f"Simulate launching of the {command} script.")
150
+
151
+
152
+ def launch_tailcuts_finder(run_id: int):
153
+ """
154
+ Launch the lstchain script to calculate the correct
155
+ tailcuts to use for a given run.
156
+ """
157
+ command = cfg.get("lstchain", "tailcuts_finder")
158
+ slurm_account = cfg.get("SLURM", "ACCOUNT")
159
+ input_dir = Path(options.directory)
160
+ output_dir = Path(cfg.get(options.tel_id, "TAILCUTS_FINDER_DIR"))
161
+ log_dir = Path(options.directory) / "log"
162
+ log_file = log_dir / f"tailcuts_finder_{run_id:05d}_%j.log"
163
+ cmd = [
164
+ "sbatch", "--parsable",
165
+ f"--account={slurm_account}",
166
+ "-o", log_file,
167
+ command,
168
+ f"--input-dir={input_dir}",
169
+ f"--run={run_id}",
170
+ f"--output-dir={output_dir}",
171
+ ]
172
+ if not options.simulate:
173
+ job = sp.run(cmd, encoding="utf-8", capture_output=True, text=True, check=True)
174
+ job_id = job.stdout.strip()
175
+ log.debug(f"Launched lstchain_find_tailcuts job {job_id} for run {run_id}!")
176
+
177
+ else:
178
+ log.info(f"Simulate launching of the {command} script.")
179
+
180
+
181
+
182
+ def tailcuts_config_file_exists(run_id: int) -> bool:
183
+ """Check if the config file created by the tailcuts finder script already exists."""
184
+ tailcuts_config_file = Path(cfg.get(options.tel_id, "TAILCUTS_FINDER_DIR")) / f"dl1ab_Run{run_id:05d}.json"
185
+ return tailcuts_config_file.exists()
186
+
187
+
188
+ def main():
189
+ """
190
+ Main script to be called as cron job. It launches the Cat-B calibration script
191
+ and the tailcuts finder script for each run of the corresponding date, and creates
192
+ the catB_{run}.closed files if Cat-B calibration has finished successfully.
193
+ """
194
+ opts = parser.parse_args()
195
+ options.tel_id = opts.tel_id
196
+ options.simulate = opts.simulate
197
+ options.date = opts.date
198
+ options.date = set_default_date_if_needed()
199
+ options.configfile = opts.config.resolve()
200
+ options.directory = analysis_path(options.tel_id)
201
+
202
+ if opts.verbose:
203
+ log.setLevel(logging.DEBUG)
204
+ else:
205
+ log.setLevel(logging.INFO)
206
+
207
+ run_summary_dir = Path(cfg.get(options.tel_id, "RUN_SUMMARY_DIR"))
208
+ run_summary = Table.read(run_summary_dir / f"RunSummary_{date_to_dir(options.date)}.ecsv")
209
+ data_runs = run_summary[run_summary["run_type"]=="DATA"]
210
+ for run_id in data_runs["run_id"]:
211
+ # first check if the dl1a files are produced
212
+ if not r0_to_dl1_step_finished_for_run(run_id):
213
+ log.info(f"The r0_to_dl1 step did not finish yet for run {run_id:05d}. Please try again later.")
214
+ else:
215
+ # launch catB calibration and tailcut finder in parallel
216
+ if cfg.getboolean("lstchain", "apply_catB_calibration") and not catB_closed_file_exists(run_id):
217
+ launch_catB_calibration(run_id)
218
+ if not cfg.getboolean("lstchain", "apply_standard_dl1b_config") and not tailcuts_config_file_exists(run_id):
219
+ launch_tailcuts_finder(run_id)
220
+
221
+
222
+ if __name__ == "__main__":
223
+ main()
@@ -16,6 +16,7 @@ from osa.configs.config import cfg
16
16
  from osa.utils.cliopts import sequencer_webmaker_argparser
17
17
  from osa.utils.logging import myLogger
18
18
  from osa.utils.utils import is_day_closed, date_to_iso, date_to_dir
19
+ from osa.paths import all_dl1ab_config_files_exist
19
20
 
20
21
  log = myLogger(logging.getLogger())
21
22
 
@@ -88,6 +89,9 @@ def get_sequencer_output(date: str, config: str, test=False, no_gainsel=False) -
88
89
  if test:
89
90
  commandargs.insert(-1, "-t")
90
91
 
92
+ if not all_dl1ab_config_files_exist(date):
93
+ commandargs.insert(-1, "--no-dl1ab")
94
+
91
95
  try:
92
96
  output = sp.run(commandargs, stdout=sp.PIPE, stderr=sp.STDOUT, encoding="utf-8", check=True)
93
97
  except sp.CalledProcessError as error:
@@ -8,7 +8,6 @@ python osa/scripts/simulate_processing.py"""
8
8
  import logging
9
9
  import multiprocessing as mp
10
10
  import subprocess
11
- from datetime import datetime
12
11
  from pathlib import Path
13
12
 
14
13
  import yaml
@@ -20,7 +19,8 @@ from osa.nightsummary.extract import build_sequences
20
19
  from osa.provenance.utils import get_log_config
21
20
  from osa.utils.cliopts import simprocparsing
22
21
  from osa.utils.logging import myLogger
23
- from osa.utils.utils import date_to_dir
22
+ from osa.utils.utils import date_to_iso
23
+ from osa.paths import analysis_path
24
24
 
25
25
  __all__ = [
26
26
  "parse_template",
@@ -174,7 +174,7 @@ def simulate_processing():
174
174
  drs4_pedestal_run_id,
175
175
  pedcal_run_id,
176
176
  sequence.run_str,
177
- options.directory,
177
+ date_to_iso(options.date),
178
178
  options.prod_id,
179
179
  ]
180
180
  log.info(f"Processing provenance for run {sequence.run_str}")
@@ -187,10 +187,7 @@ def main():
187
187
 
188
188
  simprocparsing()
189
189
 
190
- # date and tel_id hardcoded for the moment
191
- options.date = datetime.fromisoformat("2020-01-17")
192
- options.tel_id = "LST1"
193
- options.directory = date_to_dir(options.date)
190
+ options.directory = analysis_path(options.tel_id)
194
191
 
195
192
  log.info("Running simulate processing")
196
193