1"""! Tools that can be used in HPSMC jobs."""
9from subprocess
import PIPE
17 Run the SLIC Geant4 simulation.
19 Optional parameters are: **nevents**, **macros**, **run_number**, **disable_particle_table** \n
20 Required parameters are: **detector** \n
21 Required configurations are: **slic_dir**, **detector_dir**
37 self, name=
"slic", command=
"slic", output_ext=
".slcio", **kwargs
42 Setup command arguments.
43 @return list of arguments
46 raise Exception(
"No inputs given for SLIC.")
61 args.extend([
"-m",
"run_number.mac"])
65 if os.path.exists(tbl):
66 args.extend([
"-P", tbl])
68 raise Exception(
"SLIC particle.tbl does not exist: %s" % tbl)
73 if macro ==
"run_number.mac":
74 raise Exception(
"Macro name '%s' is not allowed." % macro)
75 if not os.path.isabs(macro):
76 raise Exception(
"Macro '%s' is not an absolute path." % macro)
77 args.extend([
"-m", macro])
84 """! Return path to detector file."""
88 """! Return path to particle table."""
89 return os.path.join(self.
slic_dir,
"share",
"particle.tbl")
92 """! Configure SLIC component."""
98 raise Exception(
"Failed to find valid detector_dir")
100 "Using detector_dir from install: {}".format(self.
detector_dir)
104 """! Setup SLIC component."""
105 if not os.path.exists(self.
slic_dir):
106 raise Exception(
"slic_dir does not exist: %s" % self.
slic_dir)
110 raise Exception(
"SLIC setup script does not exist: %s" % self.
namename)
113 run_number_cmd =
"/lcio/runNumber %d" % self.
run_number
114 run_number_mac = open(
"run_number.mac",
"w")
115 run_number_mac.write(run_number_cmd)
116 run_number_mac.close()
120 Return list of optional parameters.
122 Optional parameters are: **nevents**, **macros**, **run_number**
123 @return list of optional parameters
125 return [
"nevents",
"macros",
"run_number",
"disable_particle_table"]
129 Return list of required parameters.
131 Required parameters are: **detector**
132 @return list of required parameters
138 Return list of required configurations.
140 Required configurations are: **slic_dir**, **detector_dir**
141 @return list of required configurations
143 return [
"slic_dir",
"detector_dir"]
147 Execute SLIC component.
149 Component is executed by creating command line input
150 from command and command arguments.
151 @return return code of process
154 cl =
'bash -c ". %s && %s %s"' % (
161 proc = subprocess.Popen(cl, shell=
True, stdout=log_out, stderr=log_err)
165 return proc.returncode
170 Copy the SQLite database file to the desired location.
175 Initialize SQLiteProc to copy the SQLite file.
183 "Setting SQLite local copy source file from config: %s"
194 Component.__init__(self, name=
"sqlite_file_copy", **kwargs)
198 Return dummy command arguments to satisfy the parent class.
200 cmd_args = [
"(no-command-needed)"]
202 if not all(isinstance(arg, str)
for arg
in cmd_args):
203 raise ValueError(
"All arguments must be strings.")
209 Execute the file copy operation.
216 f
"Copying file from {self.source_file} to {self.destination_file}"
221 self.
logger.info(f
"Successfully copied file to {self.destination_file}")
225 except Exception
as e:
226 self.
logger.error(f
"Error during file copy: {e}")
232 Run the hps-java JobManager class.
234 Input files have slcio format.
236 Required parameters are: **steering_files** \n
237 Optional parameters are: **detector**, **run_number**, **defs**
269 if "overlay_file" in kwargs:
278 description=
"HPS Java Job Manager",
287 "Append token for '%s' automatically set to '%s' from steering key."
292 """! Configure JobManager component."""
296 if os.getenv(
"HPS_JAVA_BIN_JAR",
None)
is not None:
299 "Set HPS_JAVA_BIN_JAR from environment: {}".format(
305 "hps_java_bin_jar not set in environment or config file!"
308 if os.getenv(
"CONDITIONS_URL",
None)
is not None:
311 "Set CONDITIONS_URL from environment: {}".format(
318 Return list of required configurations.
320 Required configurations are: **hps_java_bin_jar**
321 @retun list of required configurations.
323 return [
"hps_java_bin_jar"]
326 """! Setup JobManager component."""
328 raise Exception(
"No inputs provided to hps-java.")
338 Setup command arguments.
339 @return list of arguments
344 self.
logger.debug(
"Setting java_args from config: %s" % self.
java_args)
365 self.
logger.debug(
"Setting conditions_password from config (not shown)")
391 args.append(
"outputFile=" + os.path.splitext(self.
output_files()[0])[0])
394 for k, v
in self.
defs.items():
396 args.append(k +
"=" + str(v))
401 "Steering does not exist at '%s' so assuming it is a resource."
407 "Steering looks like a file but is not an abs path: %s"
418 args.append(input_file)
422 args.append(
"overlayFile=" + os.path.splitext(self.
overlay_file)[0])
428 Return list of required parameters.
430 Required parameters are: **steering_files**
431 @return list of required parameters
433 return [
"steering_files"]
437 Return list of optional parameters.
439 Optional parameters are: **detector**, **run_number**, **defs**
440 @return list of optional parameters
442 return [
"detector",
"run_number",
"defs",
"nevents"]
447 Run the hpstr analysis tool.
449 Required parameters are: **config_files** \n
450 Optional parameters are: **year**, **is_data**, **nevents** \n
451 Required configs are: **hpstr_install_dir**, **hpstr_base**
454 def __init__(self, cfg=None, is_data=0, year=None, tracking=None, **kwargs):
467 Component.__init__(self, name=
"hpstr", command=
"hpstr", **kwargs)
470 """! Setup HPSTR component."""
485 if len(os.path.dirname(config_file)):
487 if os.path.isabs(config_file):
492 "The config has a directory but is not an abs path: %s" % self.
cfg
497 self.
hpstr_base,
"processors",
"config", config_file
502 if os.path.splitext(self.
input_files()[0])[1] ==
".root":
510 Return list of required parameters.
512 Required parameters are: **config_files**
513 @return list of required parameters
515 return [
"config_files"]
519 Return list of optional parameters.
521 Optional parameters are: **year**, **is_data**, **nevents**
522 @return list of optional parameters
524 return [
"year",
"is_data",
"nevents",
"tracking"]
528 Return list of required configs.
530 Required configs are: **hpstr_install_dir**, **hpstr_base**
531 @return list of required configs
533 return [
"hpstr_install_dir",
"hpstr_base"]
537 Setup command arguments.
538 @return list of arguments
551 if self.
year is not None:
552 args.extend([
"-y", str(self.
year)])
554 args.extend([
"-w", str(self.
tracking)])
558 """! Adjust names of output files."""
561 return [
"%s.root" % f]
568 """! Execute HPSTR component."""
570 cl =
'bash -c ". %s && %s %s"' % (
577 proc = subprocess.Popen(cl, shell=
True, stdout=log_out, stderr=log_err)
581 return proc.returncode
589 Generic class for StdHep tools.
597 "lhe_tridents_displacetime",
598 "lhe_tridents_displaceuni",
606 Component.__init__(self, name=name, command=
"stdhep_" + name, **kwargs)
610 Setup command arguments.
611 @return list of arguments
615 if self.
name in StdHepTool.seed_names:
621 raise Exception(
"Too many outputs specified for StdHepTool.")
623 raise Exception(
"No outputs specified for StdHepTool.")
626 for i
in self.
inputs[::-1]:
629 raise Exception(
"No inputs specified for StdHepTool.")
636 Transform StdHep events into beam coordinates.
638 Optional parameters are: **beam_sigma_x**, **beam_sigma_y**, **beam_rot_x**,
639 **beam_rot_y**, **beam_rot_z**, **target_x**, **target_y**, **target_z**
661 StdHepTool.__init__(self, name=
"beam_coords", append_tok=
"rot", **kwargs)
665 Setup command arguments.
666 @return list of arguments
668 args = StdHepTool.cmd_args(self)
683 args.extend([
"-X", str(self.
target_x)])
685 args.extend([
"-Y", str(self.
target_y)])
687 args.extend([
"-Z", str(self.
target_z)])
693 Return list of optional parameters.
695 Optional parameters are: **beam_sigma_x**, **beam_sigma_y**, **beam_rot_x**,
696 **beam_rot_y**, **beam_rot_z**, **target_x**, **target_y**, **target_z**
697 @return list of optional parameters
713 Randomly sample StdHep events into a new file.
715 Optional parameters are: **nevents**, **mu**
719 StdHepTool.__init__(self, name=
"random_sample", append_tok=
"sampled", **kwargs)
725 Setup command arguments.
726 @return list of arguments
730 if self.
name in StdHepTool.seed_names:
733 args.extend([
"-N", str(1)])
738 if self.
mu is not None:
739 args.extend([
"-m", str(self.
mu)])
743 args.insert(0, os.path.splitext(self.
output_files()[0])[0])
745 raise Exception(
"Too many outputs specified for RandomSample.")
747 raise Exception(
"No outputs specified for RandomSample.")
750 for i
in self.
inputs[::-1]:
753 raise Exception(
"No inputs were provided.")
759 Return list of optional parameters.
761 Optional parameters are: **nevents**, **mu**
762 @return list of optional parameters
764 return [
"nevents",
"mu"]
767 """! Execute RandomSample component"""
768 returncode = Component.execute(self, log_out, log_err)
771 src =
"%s_1.stdhep" % os.path.splitext(self.
output_files()[0])[0]
772 dest =
"%s.stdhep" % os.path.splitext(self.
output_files()[0])[0]
773 self.
logger.debug(
"Moving '%s' to '%s'" % (src, dest))
774 shutil.move(src, dest)
781 Convert LHE files to StdHep, displacing the time by given ctau.
783 Optional parameters are: **ctau**
790 self, name=
"lhe_tridents_displacetime", output_ext=
".stdhep", **kwargs
795 Setup command arguments.
796 @return list of arguments
798 args = StdHepTool.cmd_args(self)
799 if self.
ctau is not None:
800 args.extend([
"-l", str(self.
ctau)])
805 Return list of optional parameters.
807 Optional parameters are: **ctau**
808 @return list of optional parameters
815 Convert LHE files to StdHep, displacing the time by given ctau.
817 Optional parameters are: **ctau**
824 self, name=
"lhe_tridents_displaceuni", output_ext=
".stdhep", **kwargs
829 Setup command arguments.
830 @return list of arguments
832 args = StdHepTool.cmd_args(self)
833 if self.
ctau is not None:
834 args.extend([
"-l", str(self.
ctau)])
839 Return list of optional parameters.
841 Optional parameters are: **ctau**
842 @return list of optional parameters
849 Add mother particles for physics samples.
853 StdHepTool.__init__(self, name=
"add_mother", append_tok=
"mom", **kwargs)
857 """! Add full truth mother particles for physics samples"""
861 self,
"add_mother_full_truth", append_tok=
"mom_full_truth", **kwargs
865 "Must have 2 input files: a stdhep file and a lhe file in order"
870 raise Exception(
"The first input file must be a stdhep file")
874 raise Exception(
"The second input file must be a lhe file")
878 Setup command arguments.
879 @return list of arguments
886 Merge StdHep files, applying poisson sampling.
888 Required parameters are: **target_thickness**, **num_electrons**
899 StdHepTool.__init__(self, name=
"merge_poisson", append_tok=
"sampled", **kwargs)
902 """! Setup MergePoisson component."""
906 raise Exception(
"Cross section is missing.")
907 self.
logger.info(
"mu is %f", self.
mu)
911 Return list of required parameters.
913 Required parameters are: **target_thickness**, **num_electrons**
914 @return list of required parameters
916 return [
"target_thickness",
"num_electrons"]
920 Setup command arguments.
921 @return list of arguments
924 if self.
name in StdHepTool.seed_names:
931 args.insert(0, os.path.splitext(self.
output_files()[0])[0])
933 raise Exception(
"Too many outputs specified for MergePoisson.")
935 raise Exception(
"No outputs specified for MergePoisson.")
938 for i
in self.
inputs[::-1]:
941 raise Exception(
"No inputs were provided.")
946 """! Execute MergePoisson component."""
947 returncode = Component.execute(self, log_out, log_err)
950 src =
"%s_1.stdhep" % os.path.splitext(self.
output_files()[0])[0]
951 dest =
"%s.stdhep" % os.path.splitext(self.
output_files()[0])[0]
952 self.
logger.debug(
"Moving '%s' to '%s'" % (src, dest))
953 shutil.move(src, dest)
962 Optional parameters are: none \n
963 Required parameters are: none
967 StdHepTool.__init__(self, name=
"merge_files", **kwargs)
971 Return list of optional parameters.
973 Optional parameters are: none
974 @return list of optional parameters
980 Return list of required parameters.
982 Required parameters are: none
983 @return list of required parameters
990 Count number of events in a StdHep file.
995 self, name=
"stdhep_count", command=
"stdhep_count.sh", **kwargs
1000 Setup command arguments.
1001 @return list of arguments
1007 """! Execute StdHepCount component."""
1010 proc = subprocess.Popen(cl, stdout=PIPE)
1011 (output, err) = proc.communicate()
1013 nevents = int(output.split()[1])
1014 print(
"StdHep file '%s' has %d events." % (self.
input_files()[0], nevents))
1016 return proc.returncode
1021 Generic base class for Java based tools.
1031 Component.__init__(self, name,
"java", **kwargs)
1035 Return list of required config.
1037 Required config are: **hps_java_bin_jar**
1038 @return list of required config
1040 return [
"hps_java_bin_jar"]
1044 Setup command arguments.
1045 @return list of arguments
1049 self.
logger.debug(
"Setting java_args from config: %s" + self.
java_args)
1067 Convert EVIO events to LCIO using the hps-java EvioToLcio command line tool.
1069 Input files have evio format (format used by DAQ system).
1071 Required parameters are: **detector**, **steering_files** \n
1072 Optional parameters are: **run_number**, **skip_events**, **nevents**, **event_print_interval**
1089 name=
"evio_to_lcio",
1090 java_class=
"org.hps.evio.EvioToLcio",
1091 output_ext=
".slcio",
1097 Return list of required parameters.
1099 Required parameters are: **detector**, **steering_files**
1100 @return list of required parameters
1102 return [
"detector",
"steering_files"]
1106 Return list of optional parameters.
1108 Optional parameters are: **run_number**, **skip_events**, **nevents**, **event_print_interval**
1109 @return list of optional parameters
1111 return [
"run_number",
"skip_events",
"nevents",
"event_print_interval"]
1114 """! Setup EvioToLcio component."""
1124 Setup command arguments.
1125 @return list of arguments
1127 args = JavaTool.cmd_args(self)
1129 raise Exception(
"No output files were provided.")
1131 args.append(
"-DoutputFile=%s" % os.path.splitext(output_file)[0])
1141 "Steering does not exist at '%s' so assuming it is a resource."
1147 "Steering looks like a file but is not an abs path: %s"
1158 args.append(inputfile)
1168 Space MC events and apply energy filters to process before readout.
1170 Optional parameters are: **filter_ecal_hit_ecut**, **filter_event_interval**,
1171 **filter_nevents_read**, **filter_nevents_write**, **filter_no_cuts** \n
1172 Required config are: **hps_java_bin_jar**
1176 if "filter_no_cuts" in kwargs:
1182 if "filter_ecal_pairs" in kwargs:
1187 if "filter_ecal_hit_ecut" in kwargs:
1194 if "filter_event_interval" in kwargs:
1200 if "filter_nevents_read" in kwargs:
1206 if "filter_nevents_write" in kwargs:
1216 name=
"filter_bunches",
1217 java_class=
"org.hps.util.FilterMCBunches",
1223 """! Configure FilterBunches component."""
1226 if os.getenv(
"HPS_JAVA_BIN_JAR",
None)
is not None:
1229 "Set HPS_JAVA_BIN_JAR from environment: {}".format(
1236 Setup command arguments.
1237 @return list of arguments
1239 args = JavaTool.cmd_args(self)
1262 Return list of optional parameters.
1264 Optional parameters are: **filter_ecal_hit_ecut**, **filter_event_interval**,
1265 **filter_nevents_read**, **filter_nevents_write**, **filter_no_cuts** \n
1266 @return list of optional parameters
1269 "filter_ecal_hit_ecut",
1270 "filter_event_interval",
1271 "filter_nevents_read",
1272 "filter_nevents_write",
1278 Return list of required config.
1280 Required config are: **hps_java_bin_jar**
1281 @return list of required config
1283 return [
"hps_java_bin_jar"]
1288 Apply hodo-hit filter and space MC events to process before readout.
1290 The nevents parameter is not settable from JSON in this class. It should
1291 be supplied as an init argument in the job script if it needs to be
1292 customized (the default nevents and event_interval used to apply spacing
1293 should usually not need to be changed by the user). \n
1295 Optional parameters are: **num_hodo_hits**, **event_interval**
1299 if "num_hodo_hits" in kwargs:
1304 if "event_interval" in kwargs:
1311 name=
"filter_events",
1312 java_class=
"org.hps.util.ExtractEventsWithHitAtHodoEcal",
1319 Setup command arguments.
1320 @return list of arguments
1322 args = JavaTool.cmd_args(self)
1338 Return list of optional parameters.
1340 Optional parameters are: **num_hodo_hits**, **event_interval**
1341 @return list of optional parameters
1343 return [
"num_hodo_hits",
"event_interval"]
1348 Unzip the input files to outputs.
1352 Component.__init__(self, name=
"unzip", command=
"gunzip", **kwargs)
1355 """! Return list of output files."""
1358 return [os.path.splitext(i)[0]
for i
in self.
input_files()]
1361 """! Execute Unzip component."""
1365 with gzip.open(inputfile,
"rb")
as in_file, open(
1368 shutil.copyfileobj(in_file, out_file)
1369 self.
logger.debug(
"Unzipped '%s' to '%s'" % (inputfile, outputfile))
1375 Dump LCIO event information.
1377 Required parameters are: none \n
1378 Required config are: **lcio_dir**
1384 Component.__init__(self, name=
"lcio_dump_event", command=
"dumpevent", **kwargs)
1386 if "event_num" in kwargs:
1392 """! Configure LCIODumpEvent component."""
1398 """! Setup LCIODumpEvent component."""
1403 Setup command arguments.
1404 @return list of arguments
1407 raise Exception(
"Missing required inputs for LCIODumpEvent.")
1415 Return list of required config.
1417 Required config are: **lcio_dir**
1418 @return list of required config
1424 Return list of required parameters.
1426 Required parameters are: none
1427 @return list of required parameters
1434 Count events in an LHE file.
1437 def __init__(self, minevents=0, fail_on_underflow=False, **kwargs):
1439 Component.__init__(self, name=
"lhe_count", **kwargs)
1442 """! Setup LHECount component."""
1444 raise Exception(
"Missing at least one input file.")
1448 Check if command exists.
1449 @return True if command exists
1454 """! Execute LHECount component."""
1456 with gzip.open(i,
"rb")
as in_file:
1457 lines = in_file.readlines()
1461 if "<event>" in line:
1464 print(
"LHE file '%s' has %d events." % (i, nevents))
1467 msg =
"LHE file '%s' does not contain the minimum %d events." % (
1471 if self.fail_on_underflow:
1472 raise Exception(msg)
1480 Tar files into an archive.
1484 Component.__init__(self, name=
"tar_files", **kwargs)
1488 Check if command exists.
1489 @return True if command exists
1494 """! Execute TarFiles component."""
1495 self.
logger.debug(
"Opening '%s' for writing ..." % self.
outputs[0])
1496 tar = tarfile.open(self.
outputs[0],
"w")
1498 self.
logger.debug(
"Adding '%s' to archive" % i)
1507 Move input files to new locations.
1511 Component.__init__(self, name=
"move_files", **kwargs)
1515 Check if command exists.
1516 @return True if command exists
1521 """! Execute TarFiles component."""
1523 raise Exception(
"Input and output lists are not the same length!")
1527 self.
logger.info(
"Moving %s -> %s" % (src, dest))
1528 shutil.move(src, dest)
1534 Generic component for LCIO tools.
1536 Required parameters are: none \n
1537 Required config are: **lcio_bin_jar**
1543 Component.__init__(self, name, command=
"java", **kwargs)
1546 """! Configure LCIOTool component."""
1553 Setup command arguments.
1554 @return list of arguments
1557 raise Exception(
"Name required to write cmd args for LCIOTool.")
1562 Return list of required config.
1564 Required config are: **lcio_bin_jar**
1565 @return list of required config
1567 return [
"lcio_bin_jar"]
1571 Return list of required parameters.
1573 Required parameters are: none
1574 @return list of required parameters
1581 Concatenate LCIO files together.
1585 LCIOTool.__init__(self, name=
"concat", **kwargs)
1589 Setup command arguments.
1590 @return list of arguments
1592 args = LCIOTool.cmd_args(self)
1594 raise Exception(
"Missing at least one input file.")
1596 raise Exception(
"Missing an output file.")
1598 args.extend([
"-f", i])
1599 args.extend([
"-o", self.
outputs[0]])
1605 Count events in LCIO files.
1607 Required parameters are: none \n
1608 Optional parameters are: none
1612 LCIOTool.__init__(self, name=
"count", **kwargs)
1616 Setup command arguments.
1617 @return list of arguments
1619 args = LCIOTool.cmd_args(self)
1621 raise Exception(
"Missing an input file.")
1627 Return list of required parameters.
1629 Required parameters are: none
1630 @return list of required parameters
1636 Return list of optional parameters.
1638 Optional parameters are: none
1639 @return list of optional parameters
1650 LCIOTool.__init__(self, name=
"merge", **kwargs)
1654 Setup command arguments.
1655 @return list of arguments
1657 args = LCIOTool.cmd_args(self)
1659 raise Exception(
"Missing at least one input file.")
1661 raise Exception(
"Missing an output file.")
1663 args.extend([
"-f", i])
1664 args.extend([
"-o", self.
outputs[0]])
1671MergeROOT tool for hps-mc
1672Merges ROOT files using hadd with validation
1678 Merge ROOT files using hadd with event count validation.
1680 This component uses ROOT's hadd utility to merge multiple ROOT files
1681 into a single output file, and validates that all events are preserved.
1686 Initialize MergeROOT component.
1691 List of input ROOT files to merge
1693 List containing the output merged ROOT file name
1694 force : bool, optional
1695 Force overwrite of output file (default: True)
1696 compression : int, optional
1697 Compression level for output file (0-9, default: None uses hadd default)
1698 validate : bool, optional
1699 Validate event counts after merge (default: True)
1701 Component.__init__(self, **kwargs)
1708 if not hasattr(self,
"force"):
1712 if not hasattr(self,
"compression"):
1716 if not hasattr(self,
"validate"):
1725 Build command line arguments for hadd.
1730 List of command arguments
1746 raise RuntimeError(
"MergeROOT: No output file specified")
1752 raise RuntimeError(
"MergeROOT: No input files specified")
1758 Scan a ROOT file and extract TTree event counts.
1768 Dictionary mapping tree names to entry counts
1774 "MergeROOT: PyROOT is required for validation but not available"
1780 root_file = ROOT.TFile.Open(filename,
"READ")
1781 if not root_file
or root_file.IsZombie():
1782 raise RuntimeError(
"MergeROOT: Cannot open ROOT file: %s" % filename)
1785 for key
in root_file.GetListOfKeys():
1789 if obj.InheritsFrom(
"TTree"):
1790 tree_name = obj.GetName()
1791 num_entries = obj.GetEntries()
1792 tree_counts[tree_name] = num_entries
1800 Scan all input files and store tree event counts.
1807 log_out.write(
"\n" +
"=" * 70 +
"\n")
1808 log_out.write(
"MergeROOT: Scanning input files for TTrees\n")
1809 log_out.write(
"=" * 70 +
"\n")
1812 if not os.path.exists(input_file):
1813 raise RuntimeError(
"MergeROOT: Input file not found: %s" % input_file)
1815 log_out.write(
"\nScanning: %s\n" % input_file)
1819 log_out.write(
" WARNING: No TTrees found in this file\n")
1821 for tree_name, count
in tree_counts.items():
1822 log_out.write(
" Tree '%s': %d events\n" % (tree_name, count))
1826 log_out.write(
"\n" +
"=" * 70 +
"\n")
1831 Scan output file and store tree event counts.
1840 log_out.write(
"\n" +
"=" * 70 +
"\n")
1841 log_out.write(
"MergeROOT: Scanning output file for TTrees\n")
1842 log_out.write(
"=" * 70 +
"\n")
1843 log_out.write(
"\nScanning: %s\n" % output_file)
1848 log_out.write(
" WARNING: No TTrees found in output file\n")
1851 log_out.write(
" Tree '%s': %d events\n" % (tree_name, count))
1853 log_out.write(
"\n" +
"=" * 70 +
"\n")
1858 Validate that event counts match between input and output files.
1868 True if validation passes, False otherwise
1870 log_out.write(
"\n" +
"=" * 70 +
"\n")
1871 log_out.write(
"MergeROOT: Validating merge results\n")
1872 log_out.write(
"=" * 70 +
"\n\n")
1875 total_input_counts = {}
1878 for tree_name, count
in tree_counts.items():
1879 if tree_name
not in total_input_counts:
1880 total_input_counts[tree_name] = 0
1881 total_input_counts[tree_name] += count
1886 if not total_input_counts:
1887 log_out.write(
"WARNING: No TTrees found in input files\n")
1890 log_out.write(
"Event count validation:\n")
1891 log_out.write(
"-" * 70 +
"\n")
1893 "%-30s %15s %15s %10s\n"
1894 % (
"Tree Name",
"Input Events",
"Output Events",
"Status")
1896 log_out.write(
"-" * 70 +
"\n")
1898 for tree_name, input_count
in sorted(total_input_counts.items()):
1901 if output_count == input_count:
1908 "%-30s %15d %15d %10s\n"
1909 % (tree_name, input_count, output_count, status)
1914 total_input_counts.keys()
1917 log_out.write(
"\nWARNING: Output contains trees not found in inputs:\n")
1918 for tree_name
in extra_trees:
1920 " - %s: %d events\n"
1924 log_out.write(
"-" * 70 +
"\n")
1927 log_out.write(
"\n✓ VALIDATION PASSED: All event counts match!\n")
1929 log_out.write(
"\n✗ VALIDATION FAILED: Event count mismatch detected!\n")
1931 log_out.write(
"=" * 70 +
"\n\n")
1938 Print a summary of the merge operation.
1945 log_out.write(
"\n" +
"=" * 70 +
"\n")
1946 log_out.write(
"MergeROOT: Summary\n")
1947 log_out.write(
"=" * 70 +
"\n")
1948 log_out.write(
"Input files: %d\n" % len(self.
inputsinputs))
1951 log_out.write(
" %d. %s\n" % (i, input_file))
1955 "Compression level: %s\n"
1961 log_out.write(
"\nTotal events in merged file:\n")
1963 log_out.write(
" %-30s: %d events\n" % (tree_name, count))
1965 log_out.write(
"=" * 70 +
"\n")
1970 Execute MergeROOT component using hadd.
1982 Return code from hadd command
1986 raise RuntimeError(
"MergeROOT: hadd command not found in PATH")
1990 if not os.path.exists(input_file):
1991 raise RuntimeError(
"MergeROOT: Input file not found: %s" % input_file)
1997 except Exception
as e:
1998 log_out.write(
"\nWARNING: Could not scan input files: %s\n" % str(e))
1999 log_out.write(
"Proceeding with merge without validation.\n")
2006 log_out.write(
"\n" +
"=" * 70 +
"\n")
2007 log_out.write(
"MergeROOT: Executing hadd\n")
2008 log_out.write(
"=" * 70 +
"\n")
2009 log_out.write(
"Command: %s\n" %
" ".join(cmd))
2010 log_out.write(
"=" * 70 +
"\n\n")
2014 proc = subprocess.Popen(cmd, stdout=log_out, stderr=log_err)
2018 if proc.returncode != 0:
2020 "MergeROOT: hadd failed with return code %d" % proc.returncode
2026 "MergeROOT: Output file was not created: %s" % self.
outputsoutputs[0]
2029 log_out.write(
"\n✓ hadd completed successfully\n")
2037 if not validation_passed:
2038 raise RuntimeError(
"MergeROOT: Event count validation failed!")
2040 except Exception
as e:
2041 log_out.write(
"\nERROR during validation: %s\n" % str(e))
2047 return proc.returncode
2051 Return list of output files.
2056 List containing the merged output ROOT file
2062 Return list of required configuration parameters.
2067 List of required config parameters (empty for MergeROOT)
Base class for components in a job.
output_files(self)
Return a list of output files created by this component.
config_from_environ(self)
Configure component from environment variables which are just upper case versions of the required con...
cmd_exists(self)
Check if the component's assigned command exists.
cmd_args(self)
Return the command arguments of this component.
input_files(self)
Get a list of input files for this component.
Miscellaneous math functions.