lstosa 0.10.17__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.17.dist-info → lstosa-0.10.19.dist-info}/METADATA +4 -4
  2. {lstosa-0.10.17.dist-info → lstosa-0.10.19.dist-info}/RECORD +41 -40
  3. {lstosa-0.10.17.dist-info → lstosa-0.10.19.dist-info}/WHEEL +1 -1
  4. {lstosa-0.10.17.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 +16 -6
  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 +13 -8
  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 +76 -27
  29. osa/scripts/update_source_catalog.py +5 -2
  30. osa/tests/test_jobs.py +29 -12
  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.17.dist-info → lstosa-0.10.19.dist-info}/LICENSE +0 -0
  41. {lstosa-0.10.17.dist-info → lstosa-0.10.19.dist-info}/top_level.txt +0 -0
osa/paths.py CHANGED
@@ -2,17 +2,17 @@
2
2
 
3
3
  import logging
4
4
  import re
5
+ import sys
5
6
  from datetime import datetime
6
7
  from pathlib import Path
7
8
  from typing import List
8
9
  import subprocess
9
10
  import time
10
-
11
+ import json
11
12
  import lstchain
12
13
  from astropy.table import Table
13
14
  from lstchain.onsite import (find_systematics_correction_file,
14
- find_time_calibration_file,
15
- find_filter_wheels)
15
+ find_time_calibration_file)
16
16
 
17
17
  from osa.configs import options
18
18
  from osa.configs.config import DEFAULT_CFG, cfg
@@ -45,8 +45,8 @@ __all__ = [
45
45
 
46
46
 
47
47
  DATACHECK_WEB_BASEDIR = Path(cfg.get("WEBSERVER", "DATACHECK"))
48
- CALIB_BASEDIR = Path(cfg.get("LST1", "CALIB_DIR"))
49
- DRS4_PEDESTAL_BASEDIR = Path(cfg.get("LST1", "PEDESTAL_DIR"))
48
+ CALIB_BASEDIR = Path(cfg.get("LST1", "CAT_A_CALIB_DIR"))
49
+ DRS4_PEDESTAL_BASEDIR = Path(cfg.get("LST1", "CAT_A_PEDESTAL_DIR"))
50
50
 
51
51
 
52
52
  def analysis_path(tel) -> Path:
@@ -136,18 +136,7 @@ def get_calibration_filename(run_id: int, prod_id: str) -> Path:
136
136
  return files[-1] # Get the latest production among the major lstchain version
137
137
 
138
138
  date = utils.date_to_dir(get_run_date(run_id))
139
-
140
- if options.test: # Run tests avoiding the access to the database
141
- options.filters = 52
142
-
143
- else:
144
- mongodb = cfg.get("database", "caco_db")
145
- try:
146
- # Cast run_id to int to avoid problems with numpy int64 encoding in MongoDB
147
- options.filters = find_filter_wheels(int(run_id), mongodb)
148
- except IOError:
149
- log.warning("No filter information found in database. Assuming positions 52.")
150
- options.filters = 52
139
+ options.filters = utils.get_calib_filters(run_id)
151
140
 
152
141
  return (
153
142
  CALIB_BASEDIR
@@ -156,6 +145,15 @@ def get_calibration_filename(run_id: int, prod_id: str) -> Path:
156
145
  ).resolve()
157
146
 
158
147
 
148
+ def get_catB_calibration_filename(run_id: int) -> Path:
149
+ """Return the Category-B calibration filename of a given run."""
150
+ date = utils.date_to_dir(options.date)
151
+ calib_prod_id = utils.get_lstchain_version()
152
+ catB_calib_dir = Path(cfg.get("LST1", "CAT_B_CALIB_BASE")) / "calibration" / date / calib_prod_id
153
+ filters = utils.get_calib_filters(run_id)
154
+ return catB_calib_dir / f"cat_B_calibration_filters_{filters}.Run{run_id:05d}.h5"
155
+
156
+
159
157
  def pedestal_ids_file_exists(run_id: int) -> bool:
160
158
  """Look for the files with pedestal interleaved event identification."""
161
159
  pedestal_ids_dir = Path(cfg.get("LST1", "PEDESTAL_FINDER_DIR"))
@@ -256,7 +254,10 @@ def sequence_calibration_files(sequence_list: List[Sequence]) -> None:
256
254
 
257
255
  def get_datacheck_files(pattern: str, directory: Path) -> list:
258
256
  """Return a list of files matching the pattern."""
259
- return sorted(directory.glob(pattern))
257
+ if pattern=="datacheck_dl1*.pdf":
258
+ return sorted(directory.glob("tailcut*/datacheck/"+pattern))
259
+ else:
260
+ return sorted(directory.glob(pattern))
260
261
 
261
262
 
262
263
  def datacheck_directory(data_type: str, date: str) -> Path:
@@ -264,7 +265,7 @@ def datacheck_directory(data_type: str, date: str) -> Path:
264
265
  if data_type in {"PEDESTAL", "CALIB"}:
265
266
  directory = Path(cfg.get("LST1", f"{data_type}_DIR")) / date / "pro/log"
266
267
  elif data_type == "DL1AB":
267
- directory = destination_dir("DATACHECK", create_dir=False)
268
+ directory = Path(cfg.get("LST1", f"{data_type}_DIR")) / date / options.prod_id
268
269
  elif data_type == "LONGTERM":
269
270
  directory = Path(cfg.get("LST1", f"{data_type}_DIR")) / options.prod_id / date
270
271
  else:
@@ -272,7 +273,7 @@ def datacheck_directory(data_type: str, date: str) -> Path:
272
273
  return directory
273
274
 
274
275
 
275
- def destination_dir(concept: str, create_dir: bool = True) -> Path:
276
+ def destination_dir(concept: str, create_dir: bool = True, dl1_prod_id: str = None, dl2_prod_id: str = None) -> Path:
276
277
  """
277
278
  Create final destination directory for each data level.
278
279
  See Also osa.utils.register_run_concept_files
@@ -303,7 +304,7 @@ def destination_dir(concept: str, create_dir: bool = True) -> Path:
303
304
  Path(cfg.get(options.tel_id, "DL1_DIR"))
304
305
  / nightdir
305
306
  / options.prod_id
306
- / options.dl1_prod_id
307
+ / dl1_prod_id
307
308
  / "datacheck"
308
309
  )
309
310
  elif concept == "DL1AB":
@@ -311,13 +312,14 @@ def destination_dir(concept: str, create_dir: bool = True) -> Path:
311
312
  Path(cfg.get(options.tel_id, "DL1_DIR"))
312
313
  / nightdir
313
314
  / options.prod_id
314
- / options.dl1_prod_id
315
+ / dl1_prod_id
315
316
  )
316
317
  elif concept in {"DL2", "DL3"}:
317
318
  directory = (
318
319
  (Path(cfg.get(options.tel_id, f"{concept}_DIR")) / nightdir)
319
320
  / options.prod_id
320
- ) / options.dl2_prod_id
321
+ / dl2_prod_id
322
+ )
321
323
  elif concept in {"PEDESTAL", "CALIB", "TIMECALIB"}:
322
324
  directory = (
323
325
  Path(cfg.get(options.tel_id, f"{concept}_DIR"))
@@ -397,6 +399,7 @@ def create_longterm_symlink(cherenkov_job_id: str = None):
397
399
  else:
398
400
  log.warning(f"Job {cherenkov_job_id} (lstchain_cherenkov_transparency) did not finish successfully.")
399
401
 
402
+
400
403
  def dl1_datacheck_longterm_file_exits() -> bool:
401
404
  """Return true if the longterm DL1 datacheck file was already produced."""
402
405
  nightdir = utils.date_to_dir(options.date)
@@ -404,3 +407,79 @@ def dl1_datacheck_longterm_file_exits() -> bool:
404
407
  longterm_file = longterm_dir / options.prod_id / nightdir / f"DL1_datacheck_{nightdir}.h5"
405
408
  return longterm_file.exists()
406
409
 
410
+
411
+ def catB_closed_file_exists(run_id: int) -> bool:
412
+ catB_closed_file = Path(options.directory) / f"catB_{run_id:05d}.closed"
413
+ return catB_closed_file.exists()
414
+
415
+
416
+ def catB_calibration_file_exists(run_id: int) -> bool:
417
+ catB_calib_base_dir = Path(cfg.get("LST1","CAT_B_CALIB_BASE"))
418
+ prod_id = utils.get_lstchain_version()
419
+ night_dir = utils.date_to_dir(options.date)
420
+ filters = utils.get_calib_filters(run_id)
421
+ catB_calib_dir = catB_calib_base_dir / "calibration" / night_dir / prod_id
422
+ catB_calib_file = catB_calib_dir / f"cat_B_calibration_filters_{filters}.Run{run_id:05d}.h5"
423
+ return catB_calib_file.exists()
424
+
425
+
426
+ def get_dl1_prod_id(config_filename):
427
+ with open(config_filename) as json_file:
428
+ data = json.load(json_file)
429
+
430
+ picture_thresh = data["tailcuts_clean_with_pedestal_threshold"]["picture_thresh"]
431
+ boundary_thresh = data["tailcuts_clean_with_pedestal_threshold"]["boundary_thresh"]
432
+
433
+ if boundary_thresh == 4:
434
+ return f"tailcut{picture_thresh}{boundary_thresh}"
435
+ else:
436
+ return f"tailcut{picture_thresh}{boundary_thresh:02d}"
437
+
438
+
439
+ def get_dl2_nsb_prod_id(rf_model: Path) -> str:
440
+ match = re.search(r'nsb_tuning_\d+\.\d+', str(rf_model))
441
+ if not match:
442
+ log.warning(f"No 'nsb_tuning_X.XX' pattern found in the path:\n{rf_model}")
443
+ sys.exit(1)
444
+ else:
445
+ return match.group(0)
446
+
447
+
448
+ def get_dl1_prod_id_and_config(run_id: int) -> str:
449
+ if not cfg.getboolean("lstchain", "apply_standard_dl1b_config"):
450
+ tailcuts_finder_dir = Path(cfg.get(options.tel_id, "TAILCUTS_FINDER_DIR"))
451
+ dl1b_config_file = tailcuts_finder_dir / f"dl1ab_Run{run_id:05d}.json"
452
+ if not dl1b_config_file.exists() and not options.simulate:
453
+ log.error(
454
+ f"The dl1b config file was not created yet for run {run_id:05d}. "
455
+ "Please try again later."
456
+ )
457
+ sys.exit(1)
458
+ else:
459
+ dl1_prod_id = get_dl1_prod_id(dl1b_config_file)
460
+ return dl1_prod_id, dl1b_config_file.resolve()
461
+ else:
462
+ dl1b_config_file = Path(cfg.get("lstchain", "dl1b_config"))
463
+ dl1_prod_id = cfg.get("LST1", "DL1_PROD_ID")
464
+ return dl1_prod_id, dl1b_config_file.resolve()
465
+
466
+
467
+ def get_dl2_prod_id(run_id: int) -> str:
468
+ dl1_prod_id = get_dl1_prod_id_and_config(run_id)[0]
469
+ rf_model = utils.get_RF_model(run_id)
470
+ nsb_prod_id = get_dl2_nsb_prod_id(rf_model)
471
+ return f"{dl1_prod_id}/{nsb_prod_id}"
472
+
473
+
474
+ def all_dl1ab_config_files_exist(date: str) -> bool:
475
+ nightdir = date.replace("-","")
476
+ run_summary_dir = Path(cfg.get(options.tel_id, "RUN_SUMMARY_DIR"))
477
+ run_summary_file = run_summary_dir / f"RunSummary_{nightdir}.ecsv"
478
+ summary_table = Table.read(run_summary_file)
479
+ data_runs = summary_table[summary_table["run_type"] == "DATA"]
480
+ for run_id in data_runs["run_id"]:
481
+ tailcuts_finder_dir = Path(cfg.get(options.tel_id, "TAILCUTS_FINDER_DIR"))
482
+ dl1b_config_file = tailcuts_finder_dir / f"dl1ab_Run{run_id:05d}.json"
483
+ if not dl1b_config_file.exists():
484
+ return False
485
+ return True
osa/provenance/capture.py CHANGED
@@ -53,7 +53,7 @@ LOG_FILENAME = provconfig["handlers"]["provHandler"]["filename"]
53
53
  PROV_PREFIX = provconfig["PREFIX"]
54
54
  SUPPORTED_HASH_METHOD = ["md5"]
55
55
  SUPPORTED_HASH_BUFFER = ["content", "path"]
56
- REDUCTION_TASKS = ["r0_to_dl1", "dl1ab", "dl1_datacheck", "dl1_to_dl2"]
56
+ REDUCTION_TASKS = ["r0_to_dl1", "catB_calibration", "dl1ab", "dl1_datacheck", "dl1_to_dl2"]
57
57
 
58
58
  # global variables
59
59
  traced_entities = {}
@@ -200,6 +200,13 @@ activities:
200
200
  # filepath: /fefs/aswg/data/real/DL1/20200218/v0.4.3_v00/
201
201
  # size: 128
202
202
 
203
+ catB_calibration:
204
+ description:
205
+ "Create Cat-B calibration file for an observation run"
206
+ parameters:
207
+ usage:
208
+ generation:
209
+
203
210
  dl1ab:
204
211
  description:
205
212
  "Create DL1AB files for an observation run"
osa/provenance/utils.py CHANGED
@@ -10,7 +10,7 @@ from osa.utils.utils import date_to_dir, get_lstchain_version
10
10
 
11
11
  __all__ = ["parse_variables", "get_log_config"]
12
12
 
13
- REDUCTION_TASKS = ["r0_to_dl1", "dl1ab", "dl1_datacheck", "dl1_to_dl2"]
13
+ REDUCTION_TASKS = ["r0_to_dl1", "catB_calibration", "dl1ab", "dl1_datacheck", "dl1_to_dl2"]
14
14
 
15
15
 
16
16
  def parse_variables(class_instance):
@@ -40,20 +40,18 @@ def parse_variables(class_instance):
40
40
  configfile_dl1b = cfg.get("lstchain", "dl1b_config")
41
41
  configfile_dl2 = cfg.get("lstchain", "dl2_config")
42
42
  raw_dir = Path(cfg.get("LST1", "R0_DIR"))
43
- rf_models_directory = Path(cfg.get("lstchain", "RF_MODELS"))
43
+ rf_models_directory = Path(cfg.get("LST1", "RF_MODELS"))
44
44
  dl1_dir = Path(cfg.get("LST1", "DL1_DIR"))
45
45
  dl2_dir = Path(cfg.get("LST1", "DL2_DIR"))
46
- calib_dir = Path(cfg.get("LST1", "CALIB_DIR"))
47
- pedestal_dir = Path(cfg.get("LST1", "PEDESTAL_DIR"))
46
+ calib_dir = Path(cfg.get("LST1", "CAT_A_CALIB_DIR"))
47
+ pedestal_dir = Path(cfg.get("LST1", "CAT_A_PEDESTAL_DIR"))
48
48
 
49
49
  class_instance.SoftwareVersion = get_lstchain_version()
50
50
  class_instance.ProcessingConfigFile = str(options.configfile)
51
51
  class_instance.ObservationDate = flat_date
52
52
  if class_instance.__name__ in REDUCTION_TASKS:
53
53
  muon_dir = dl1_dir / flat_date / options.prod_id / "muons"
54
- outdir_dl1 = dl1_dir / flat_date / options.prod_id / options.dl1_prod_id
55
- outdir_dl2 = dl2_dir / flat_date / options.prod_id / options.dl2_prod_id
56
-
54
+
57
55
  if class_instance.__name__ in ["drs4_pedestal", "calibrate_charge"]:
58
56
  # drs4_pedestal_run_id [0] 1804
59
57
  # pedcal_run_id [1] 1805
@@ -111,6 +109,7 @@ def parse_variables(class_instance):
111
109
  run = run_subrun.split(".")[0]
112
110
  class_instance.ObservationRun = run
113
111
 
112
+ outdir_dl1 = dl1_dir / flat_date / options.prod_id
114
113
  calibration_file = Path(class_instance.args[0]).resolve()
115
114
  pedestal_file = Path(class_instance.args[1]).resolve()
116
115
  timecalibration_file = Path(class_instance.args[2]).resolve()
@@ -133,10 +132,16 @@ def parse_variables(class_instance):
133
132
  class_instance.InterleavedPedestalEventsFile = None
134
133
  if class_instance.args[6] is not None:
135
134
  class_instance.InterleavedPedestalEventsFile = str(Path(class_instance.args[6]))
135
+
136
+ if class_instance.__name__ == "catB_calibration":
137
+ class_instance.ObservationRun = class_instance.args[0].split(".")[0]
136
138
 
137
139
  if class_instance.__name__ == "dl1ab":
138
140
  # run_str [0] 02006.0000
141
+ # dl1b_config [1]
142
+ # dl1_prod_id [2]
139
143
 
144
+ outdir_dl1 = dl1_dir / flat_date / options.prod_id / class_instance.args[2]
140
145
  class_instance.Analysisconfigfile_dl1 = str(Path(configfile_dl1b))
141
146
  class_instance.ObservationRun = class_instance.args[0].split(".")[0]
142
147
  class_instance.StoreImage = cfg.getboolean("lstchain", "store_image_dl1ab")
@@ -146,9 +151,12 @@ def parse_variables(class_instance):
146
151
 
147
152
  if class_instance.__name__ == "dl1_datacheck":
148
153
  # run_str [0] 02006.0000
154
+ # dl1b_prod_id [1]
155
+
149
156
  run_subrun = class_instance.args[0]
150
157
  run = run_subrun.split(".")[0]
151
158
 
159
+ outdir_dl1 = dl1_dir / flat_date / options.prod_id / class_instance.args[1]
152
160
  class_instance.ObservationRun = run
153
161
  class_instance.DL1SubrunDataset = str(
154
162
  (outdir_dl1 / f"dl1_LST-1.Run{run_subrun}.h5").resolve()
@@ -168,9 +176,16 @@ def parse_variables(class_instance):
168
176
 
169
177
  if class_instance.__name__ == "dl1_to_dl2":
170
178
  # run_str [0] 02006.0000
179
+ # rf_model_path [1]
180
+ # dl1_prod_id [2]
181
+ # dl2_prod_id [3]
182
+
171
183
  run_subrun = class_instance.args[0]
172
184
  run = run_subrun.split(".")[0]
173
185
 
186
+ outdir_dl1 = dl1_dir / flat_date / options.prod_id / class_instance.args[2]
187
+ outdir_dl2 = dl2_dir / flat_date / options.prod_id / class_instance.args[3]
188
+
174
189
  class_instance.Analysisconfigfile_dl2 = configfile_dl2
175
190
  class_instance.ObservationRun = run
176
191
  class_instance.RFModelEnergyFile = str((rf_models_directory / "reg_energy.sav").resolve())
osa/scripts/autocloser.py CHANGED
@@ -266,21 +266,11 @@ class Sequence:
266
266
 
267
267
  def is_100(self, no_dl2: bool):
268
268
  """Check that all analysis products are 100% complete."""
269
- if (
270
- no_dl2
271
- and self.dict_sequence["Tel"] != "ST"
272
- and self.dict_sequence["DL1%"] == "100"
273
- and self.dict_sequence["DL1AB%"] == "100"
274
- and self.dict_sequence["MUONS%"] == "100"
275
- ):
276
- return True
277
-
278
269
  if (
279
270
  self.dict_sequence["Tel"] != "ST"
280
271
  and self.dict_sequence["DL1%"] == "100"
281
272
  and self.dict_sequence["DL1AB%"] == "100"
282
273
  and self.dict_sequence["MUONS%"] == "100"
283
- and self.dict_sequence["DL2%"] == "100"
284
274
  ):
285
275
  return True
286
276
 
@@ -44,22 +44,27 @@ def is_calibration_produced(drs4_pedestal_run_id: int, pedcal_run_id: int) -> bo
44
44
  def drs4_pedestal_command(drs4_pedestal_run_id: int) -> list:
45
45
  """Build the create_drs4_pedestal command."""
46
46
  base_dir = Path(cfg.get("LST1", "BASE")).resolve()
47
+ r0_dir = Path(cfg.get("LST1", "R0_DIR")).resolve()
48
+ command = cfg.get("lstchain", "drs4_baseline")
47
49
  return [
48
- "onsite_create_drs4_pedestal_file",
49
- f"--run_number={drs4_pedestal_run_id}",
50
- f"--base_dir={base_dir}",
50
+ command,
51
+ "-r", str(drs4_pedestal_run_id),
52
+ "-b", base_dir,
53
+ f"--r0-dir={r0_dir}",
51
54
  "--no-progress",
52
55
  ]
53
56
 
54
-
55
57
  def calibration_file_command(drs4_pedestal_run_id: int, pedcal_run_id: int) -> list:
56
58
  """Build the create_calibration_file command."""
57
59
  base_dir = Path(cfg.get("LST1", "BASE")).resolve()
60
+ r0_dir = Path(cfg.get("LST1", "R0_DIR")).resolve()
61
+ command = cfg.get("lstchain", "charge_calibration")
58
62
  cmd = [
59
- "onsite_create_calibration_file",
60
- f"--pedestal_run={drs4_pedestal_run_id}",
61
- f"--run_number={pedcal_run_id}",
62
- f"--base_dir={base_dir}",
63
+ command,
64
+ "-p", str(drs4_pedestal_run_id),
65
+ "-r", str(pedcal_run_id),
66
+ "-b", base_dir,
67
+ f"--r0-dir={r0_dir}",
63
68
  ]
64
69
  # In case of problems with trigger tagging:
65
70
  if cfg.getboolean("lstchain", "use_ff_heuristic_id"):
osa/scripts/closer.py CHANGED
@@ -169,14 +169,14 @@ def post_process(seq_tuple):
169
169
  # Extract the provenance info
170
170
  extract_provenance(seq_list)
171
171
 
172
- # Merge DL1b files run-wise
173
- merge_files(seq_list, data_level="DL1AB")
174
-
175
172
  merge_muon_files(seq_list)
176
173
 
177
- # Merge DL2 files run-wise
178
- if not options.no_dl2:
179
- merge_files(seq_list, data_level="DL2")
174
+ # Merge DL1b files run-wise
175
+ for sequence in seq_list:
176
+ dl1_merge_job_id = merge_files(sequence, data_level="DL1AB")
177
+ # Produce DL2 files run-wise
178
+ if not options.no_dl2 and sequence.type=="DATA":
179
+ dl1_to_dl2(sequence, dl1_merge_job_id)
180
180
 
181
181
  # Merge DL1 datacheck files and produce PDFs. It also produces
182
182
  # the daily datacheck report using the longterm script, and updates
@@ -215,6 +215,66 @@ def post_process(seq_tuple):
215
215
  return False
216
216
 
217
217
 
218
+ def dl1_to_dl2(sequence, dl1_merge_job_id) -> int:
219
+ """
220
+ It prepares and execute the dl1 to dl2 lstchain scripts that applies
221
+ the already trained RFs models to DL1 files. It identifies the
222
+ primary particle, reconstructs its energy and direction.
223
+
224
+ Parameters
225
+ ----------
226
+ run_str: str
227
+
228
+ Returns
229
+ -------
230
+ rc: int
231
+ """
232
+ nightdir = date_to_dir(options.date)
233
+ dl2_dir = Path(cfg.get("LST1", "DL2_DIR"))
234
+ dl2_subdirectory = dl2_dir / nightdir / options.prod_id / sequence.dl2_prod_id
235
+ dl2_file = dl2_subdirectory / f"dl2_LST-1.Run{sequence.run_str[:5]}.h5"
236
+ dl2_config = Path(cfg.get("lstchain", "dl2_config"))
237
+ dl1ab_subdirectory = Path(cfg.get("LST1", "DL1AB_DIR"))
238
+ dl1_file = dl1ab_subdirectory / nightdir / options.prod_id / sequence.dl1_prod_id / f"dl1_LST-1.Run{sequence.run_str[:5]}.h5"
239
+ log_dir = Path(options.directory) / "log"
240
+ slurm_account = cfg.get("SLURM", "ACCOUNT")
241
+
242
+ if dl2_file.exists():
243
+ log.debug(f"The dl2 file {dl2_file} already exists.")
244
+ return 0
245
+
246
+ command = cfg.get("lstchain", "dl1_to_dl2")
247
+ cmd = [
248
+ "sbatch",
249
+ "--parsable",
250
+ "--mem-per-cpu=60GB",
251
+ f"--account={slurm_account}",
252
+ "-o", f"{log_dir}/Run{sequence.run_str[:5]}_dl2_%j.out",
253
+ "-e", f"{log_dir}/Run{sequence.run_str[:5]}_dl2_%j.err",
254
+ f"--dependency=afterok:{dl1_merge_job_id}",
255
+ command,
256
+ f"--input-file={dl1_file}",
257
+ f"--output-dir={dl2_subdirectory}",
258
+ f"--path-models={sequence.rf_model}",
259
+ f"--config={dl2_config}",
260
+ ]
261
+ log.info(f"executing {cmd}")
262
+
263
+ if options.simulate:
264
+ return 0
265
+
266
+ if not options.test and shutil.which("sbatch") is not None:
267
+ job = subprocess.run(
268
+ cmd,
269
+ encoding="utf-8",
270
+ capture_output=True,
271
+ text=True,
272
+ check=True,
273
+ )
274
+ job_id = job.stdout.strip()
275
+ return job_id
276
+
277
+
218
278
  def post_process_files(seq_list: list):
219
279
  """
220
280
  Identify the different types of files, try to close the sequences
@@ -226,9 +286,7 @@ def post_process_files(seq_list: list):
226
286
  list of sequences
227
287
  """
228
288
 
229
- output_files_set = set(Path(options.directory).rglob("*Run*"))
230
-
231
- DL1AB_RE = re.compile(rf"{options.dl1_prod_id}/dl1.*.(?:h5|hdf5|hdf)")
289
+ DL1AB_RE = re.compile(r"tailcut.*/dl1.*.(?:h5|hdf5|hdf)")
232
290
  MUONS_RE = re.compile(r"muons.*.fits")
233
291
  DATACHECK_RE = re.compile(r"datacheck_dl1.*.(?:h5|hdf5|hdf)")
234
292
  INTERLEAVED_RE = re.compile(r"interleaved.*.(?:h5|hdf5|hdf)")
@@ -243,27 +301,36 @@ def post_process_files(seq_list: list):
243
301
  )
244
302
 
245
303
  if not options.no_dl2:
246
- DL2_RE = re.compile(f"{options.dl2_prod_id}/dl2.*.(?:h5|hdf5|hdf)")
304
+ DL2_RE = re.compile("tailcut.*/nsb_tuning_.*/dl2.*.(?:h5|hdf5|hdf)")
247
305
  pattern_files["DL2"] = DL2_RE
248
306
 
249
307
  for concept, pattern_re in pattern_files.items():
250
- log.info(f"Post processing {concept} files, {len(output_files_set)} files left")
308
+ for sequence in seq_list:
309
+ output_files_set = set(Path(options.directory).rglob(f"*Run{sequence.run:05d}*"))
310
+ log.info(f"Post processing {concept} files, {len(output_files_set)} files left")
311
+
312
+ if sequence.type=="DATA":
313
+ dst_path = destination_dir(
314
+ concept,
315
+ create_dir=True,
316
+ dl1_prod_id=sequence.dl1_prod_id,
317
+ dl2_prod_id=sequence.dl2_prod_id
318
+ )
251
319
 
252
- dst_path = destination_dir(concept, create_dir=True)
320
+ log.debug(f"Checking if {concept} files need to be moved to {dst_path}")
253
321
 
254
- log.debug(f"Checking if {concept} files need to be moved to {dst_path}")
322
+ for file_path in output_files_set.copy():
255
323
 
256
- for file_path in output_files_set.copy():
324
+ file = str(file_path)
257
325
 
258
- file = str(file_path)
259
- # If seqtoclose is set, we only want to close that sequence
260
- if options.seqtoclose is not None and options.seqtoclose not in file:
261
- continue
326
+ # If seqtoclose is set, we only want to close that sequence
327
+ if options.seqtoclose is not None and options.seqtoclose not in file:
328
+ continue
262
329
 
263
- if pattern_found := pattern_re.search(file):
264
- log.debug(f"Pattern {concept} found, {pattern_found} in {file}")
265
- registered_file = register_found_pattern(file_path, seq_list, concept, dst_path)
266
- output_files_set.remove(registered_file)
330
+ if pattern_found := pattern_re.search(file):
331
+ log.debug(f"Pattern {concept} found, {pattern_found} in {file}")
332
+ registered_file = register_found_pattern(file_path, seq_list, concept, dst_path)
333
+ output_files_set.remove(registered_file)
267
334
 
268
335
 
269
336
  def set_closed_with_file():
@@ -335,13 +402,13 @@ def merge_dl1_datacheck(seq_list) -> List[str]:
335
402
  log.debug("Merging dl1 datacheck files and producing PDFs")
336
403
 
337
404
  muons_dir = destination_dir("MUON", create_dir=False)
338
- datacheck_dir = destination_dir("DATACHECK", create_dir=False)
339
405
  slurm_account = cfg.get("SLURM", "ACCOUNT")
340
406
 
341
407
  list_job_id = []
342
408
 
343
409
  for sequence in seq_list:
344
410
  if sequence.type == "DATA":
411
+ datacheck_dir = destination_dir("DATACHECK", create_dir=False, dl1_prod_id=sequence.dl1_prod_id)
345
412
  cmd = [
346
413
  "sbatch",
347
414
  "--parsable",
@@ -387,7 +454,7 @@ def extract_provenance(seq_list):
387
454
  """
388
455
  log.info("Extract provenance run wise")
389
456
 
390
- nightdir = date_to_dir(options.date)
457
+ nightdir = date_to_iso(options.date)
391
458
  slurm_account = cfg.get("SLURM", "ACCOUNT")
392
459
 
393
460
  for sequence in seq_list:
@@ -431,40 +498,52 @@ def get_pattern(data_level) -> Tuple[str, str]:
431
498
  raise ValueError(f"Unknown data level {data_level}")
432
499
 
433
500
 
434
- def merge_files(sequence_list, data_level="DL2"):
501
+ def merge_files(sequence, data_level="DL2"):
435
502
  """Merge DL1b or DL2 h5 files run-wise."""
436
503
  log.info(f"Looping over the sequences and merging the {data_level} files")
437
-
438
- data_dir = destination_dir(data_level, create_dir=False)
439
504
  pattern, prefix = get_pattern(data_level)
440
505
  slurm_account = cfg.get("SLURM", "ACCOUNT")
506
+
507
+ if sequence.type == "DATA":
508
+ data_dir = destination_dir(
509
+ data_level,
510
+ create_dir=False,
511
+ dl1_prod_id=sequence.dl1_prod_id,
512
+ dl2_prod_id=sequence.dl2_prod_id
513
+ )
514
+ merged_file = Path(data_dir) / f"{prefix}_LST-1.Run{sequence.run:05d}.h5"
441
515
 
442
- for sequence in sequence_list:
443
- if sequence.type == "DATA":
444
- merged_file = Path(data_dir) / f"{prefix}_LST-1.Run{sequence.run:05d}.h5"
445
-
446
- cmd = [
447
- "sbatch",
448
- f"--account={slurm_account}",
449
- "-D",
450
- options.directory,
451
- "-o",
452
- f"log/merge_{prefix}_{sequence.run:05d}_%j.log",
453
- "lstchain_merge_hdf5_files",
454
- f"--input-dir={data_dir}",
455
- f"--output-file={merged_file}",
456
- "--no-image",
457
- "--no-progress",
458
- f"--run-number={sequence.run}",
459
- f"--pattern={pattern}",
460
- ]
516
+ cmd = [
517
+ "sbatch",
518
+ "--parsable",
519
+ f"--account={slurm_account}",
520
+ "-D",
521
+ options.directory,
522
+ "-o",
523
+ f"log/merge_{prefix}_{sequence.run:05d}_%j.log",
524
+ "lstchain_merge_hdf5_files",
525
+ f"--input-dir={data_dir}",
526
+ f"--output-file={merged_file}",
527
+ "--no-image",
528
+ "--no-progress",
529
+ f"--run-number={sequence.run}",
530
+ f"--pattern={pattern}",
531
+ ]
461
532
 
462
- log.debug(f"Executing {stringify(cmd)}")
533
+ log.debug(f"Executing {stringify(cmd)}")
463
534
 
464
- if not options.simulate and not options.test and shutil.which("sbatch") is not None:
465
- subprocess.run(cmd, check=True)
466
- else:
467
- log.debug("Simulate launching scripts")
535
+ if not options.simulate and not options.test and shutil.which("sbatch") is not None:
536
+ job = subprocess.run(
537
+ cmd,
538
+ encoding="utf-8",
539
+ capture_output=True,
540
+ text=True,
541
+ check=True,
542
+ )
543
+ job_id = job.stdout.strip()
544
+ return job_id
545
+ else:
546
+ log.debug("Simulate launching scripts")
468
547
 
469
548
 
470
549
  def merge_muon_files(sequence_list):
@@ -503,7 +582,7 @@ def merge_muon_files(sequence_list):
503
582
  def daily_longterm_cmd(parent_job_ids: List[str]) -> List[str]:
504
583
  """Build the daily longterm command."""
505
584
  nightdir = date_to_dir(options.date)
506
- datacheck_dir = destination_dir("DATACHECK", create_dir=False)
585
+ datacheck_dir = destination_dir("DATACHECK", create_dir=False, dl1_prod_id="tailcut84")
507
586
  muons_dir = destination_dir("MUON", create_dir=False)
508
587
  longterm_dir = Path(cfg.get("LST1", "LONGTERM_DIR")) / options.prod_id / nightdir
509
588
  longterm_output_file = longterm_dir / f"DL1_datacheck_{nightdir}.h5"
@@ -548,7 +627,7 @@ def daily_datacheck(cmd: List[str]):
548
627
  def cherenkov_transparency_cmd(longterm_job_id: str) -> List[str]:
549
628
  """Build the cherenkov transparency command."""
550
629
  nightdir = date_to_dir(options.date)
551
- datacheck_dir = destination_dir("DATACHECK", create_dir=False)
630
+ datacheck_dir = destination_dir("DATACHECK", create_dir=False, dl1_prod_id="tailcut84")
552
631
  longterm_dir = Path(cfg.get("LST1", "LONGTERM_DIR")) / options.prod_id / nightdir
553
632
  longterm_datacheck_file = longterm_dir / f"DL1_datacheck_{nightdir}.h5"
554
633
  slurm_account = cfg.get("SLURM", "ACCOUNT")