File indexing completed on 2026-04-27 07:28:14
0001 """
0002 ePIC software stack definitions. Define concrete classes inheriting
0003 from abstract base StackLayer and ExperimentStack from the workflows
0004 module.
0005 """
0006
0007 from dataclasses import dataclass, field
0008 from typing import List
0009
0010 from aid2e.utilities.workflows.experimental_stack import (
0011 AnaLayer,
0012 ExperimentStack,
0013 StackLayer
0014 )
0015
0016
0017 class EpicGeoLayer(StackLayer):
0018 """Geometry layer of ePIC stack"""
0019 name = "geo"
0020 command = "checkOverlaps"
0021 rule = '{command} {arguments} {inputs} {outputs}'
0022
0023 def _make_input_arg(self, inputs: List[str]) -> str:
0024 """
0025 Formats inputs for ePIC-specific geometry
0026 layer. There should be exactly one input,
0027 the geometry configuration file to run
0028 overlap check on.
0029 """
0030 if len(inputs) != 1:
0031 raise ValueError(f"EpicGeoLayer takes one input, got {len(inputs)}")
0032 return inputs[0]
0033
0034 def _make_output_arg(self, outputs: List[str]) -> str:
0035 """
0036 Formats outputs for ePIC-specific geometry
0037 layer. There should be exactly one output,
0038 the log file to store the results of the
0039 check.
0040
0041 Also adds shell code to check for overlaps/
0042 extrusions and exit if any found.
0043 """
0044 if len(outputs) != 1:
0045 raise ValueError(f"EpicGeoLayer takes one output, got {len(outputs)}")
0046 output = outputs[0]
0047
0048
0049 checks = [
0050 f' >& {output}',
0051 f'grep -F "Number of illegal overlaps/extrusions : " {output} | while IFS= read -r line; do',
0052 ' lastChar="${line: -1}"',
0053 ' if [[ $lastChar =~ ^[0-9]$ ]]; then',
0054 ' if (( lastChar > 0 )); then',
0055 ' exit 9',
0056 ' fi',
0057 ' fi',
0058 'done'
0059 ]
0060 return '\n'.join(checks)
0061
0062
0063 class EpicSimLayer(StackLayer):
0064 """Simulation layer of ePIC stack"""
0065 name = "sim"
0066 command = "npsim"
0067 rule = '{command} {arguments} {inputs} {outputs}'
0068
0069 def _make_input_arg(self, inputs: List[str]) -> str:
0070 """
0071 Formats inputs for ePIC-specific simulation
0072 layer. Applies appropriate CLI option based
0073 on file extension of input.
0074 """
0075 formatted_inputs = list()
0076 for in_file in inputs:
0077 if in_file.endswith(".py"):
0078 formatted_inputs.append(f"--steeringFile {in_file}")
0079 if in_file.endswith(".hepmc3.root") or in_file.endswith(".hepmc"):
0080 formatted_inputs.append(f"-I {in_file}")
0081 if in_file.endswith(".mac"):
0082 formatted_inputs.append(f"--macroFile {in_file}")
0083 return ' '.join(formatted_inputs)
0084
0085 def _make_output_arg(self, outputs: List[str]) -> str:
0086 """
0087 Formats outputs for ePIC-specific simulation
0088 layer.
0089 """
0090 out_arg = ' '.join(outputs)
0091 return f"--outputFile {out_arg}"
0092
0093
0094 class EpicRecLayer(StackLayer):
0095 """Reconstruction layer of ePIC stack"""
0096 name = "rec"
0097 command = "eicrecon"
0098 rule = '{command} {arguments} {outputs} {inputs}'
0099
0100 def _make_input_arg(self, inputs: List[str]) -> str:
0101 """
0102 Formats inputs for ePIC-specific reconstruction
0103 layer.
0104 """
0105 in_arg = ' '.join(inputs)
0106 return in_arg
0107
0108 def _make_output_arg(self, outputs: List[str]) -> str:
0109 """
0110 Formats outputs for ePIC-specific reconstruction
0111 layer.
0112 """
0113 formatted_outputs = list()
0114 for out_file in outputs:
0115 formatted_outputs.append(f"-Ppodio:output_file={out_file}")
0116 return ' '.join(formatted_outputs)
0117
0118
0119 class EpicAnaLayer(AnaLayer):
0120 """Analysis layer of ePIC stack"""
0121 pass
0122
0123
0124 @dataclass
0125 class EpicStack(ExperimentStack):
0126 """The ePIC software stack"""
0127 geo: EpicGeoLayer = field(default_factory = EpicGeoLayer)
0128 sim: EpicSimLayer = field(default_factory = EpicSimLayer)
0129 rec: EpicRecLayer = field(default_factory = EpicRecLayer)
0130 ana: EpicAnaLayer = field(default_factory = EpicAnaLayer)