ATLAS Offline Software
Loading...
Searching...
No Matches
PepperConfig Namespace Reference

Functions

 _resolve_pepper_executable ()
 _resolve_data_path (exe)
 _split_ext (filename)
 _open_events (path, mode="rt")
 count_lhe_events (path)
 run_pepper (process, nevents, seed, ecm, output="pepper.lhef", batch_size=None, extra_args=None)
 arrange_output (events_file, runArgs)

Variables

 log = logging.getLogger("PepperConfig")
dict _EXT_MAP
tuple _LHE_EXTS = (".lhef.gz", ".lhef")
str _TMP_LHE_NAME = "pepper_LHE_events"
int DEFAULT_BATCH_SIZE = 1000
 _EVENT_TAG = re.compile(r"^\s*<event[\s>]")

Detailed Description

Driver that invokes the LCG-installed `pepper` binary from inside an
AthGeneration job.

This module exists because the pepper_kokkos package as shipped in LCG
needs three things wired up correctly before it will run:

1. The PEPPER_DATA_PATH env var must point at the process-data
   directory (the binary's compiled-in path is the build server's
   scratch dir and doesn't exist on cvmfs). The wrapper sets it
   automatically.

2. The output filename's extension governs the format. The pepper
   binary accepts `.lhef[.gz]`, `.hepmc3[.gz]`, `.hdf5`, and `.debug`.
   For convenience `.events[.gz]` is accepted too: pepper is invoked
   with the equivalent `.lhef[.gz]` form, then symlinked back to the
   requested name.

3. The binary deadlocks in Kokkos initialisation on CPU-only nodes,
   waiting for a CUDA device. Jobs MUST run on a GPU-equipped worker.

4. Pepper's `--batch-size`/`--n-batches` options count *trial* phase-space
   points, of which only the fraction surviving unweighting is written
   out (about 15% for ppjj at 13.6 TeV). The wrapper therefore steers
   the run with `--n-events`, which is the minimum number of non-zero
   (i.e. written) events, and checks the yield afterwards.

5. "Pepper" is an LHE generator for the Gen_tf.py skeletons
   (GeneratorConfig.GenConfigHelpers.lhefGenerators). They therefore
   expect runArgs.inputGeneratorFile to name an uncompressed
   `<name>.events` file, which they link to `events.lhe` for the
   shower and count for the job metadata. arrange_output() provides
   that, in the same way as MadGraphControl.arrange_output().

Function Documentation

◆ _open_events()

_open_events ( path,
mode = "rt" )
protected
Open a (possibly gzipped) event file.

Definition at line 129 of file PepperConfig.py.

129def _open_events(path, mode="rt"):
130 """Open a (possibly gzipped) event file."""
131 if path.endswith(".gz"):
132 return gzip.open(path, mode)
133 return open(path, mode)
134
135

◆ _resolve_data_path()

_resolve_data_path ( exe)
protected
Return the directory containing the Pepper process CSVs.

Tries (in order): PEPPER_DATA_PATH env var; the share/pepper/data
directory next to the binary (1.8.x layout); the share/pepper_data
directory next to the binary (1.1.x layout).

Definition at line 94 of file PepperConfig.py.

94def _resolve_data_path(exe):
95 """Return the directory containing the Pepper process CSVs.
96
97 Tries (in order): PEPPER_DATA_PATH env var; the share/pepper/data
98 directory next to the binary (1.8.x layout); the share/pepper_data
99 directory next to the binary (1.1.x layout).
100 """
101 candidate = os.environ.get("PEPPER_DATA_PATH")
102 if candidate and os.path.isfile(os.path.join(candidate, "2j.csv")):
103 return candidate
104
105 install_root = os.path.dirname(os.path.dirname(os.path.realpath(exe)))
106 for sub in ("share/pepper/data", "share/pepper_data"):
107 candidate = os.path.join(install_root, sub)
108 if os.path.isfile(os.path.join(candidate, "2j.csv")):
109 return candidate
110
111 raise RuntimeError(
112 "Could not locate Pepper's process-data directory. "
113 "Set PEPPER_DATA_PATH explicitly to the directory containing "
114 "2j.csv, z0j.csv, etc."
115 )
116
117

◆ _resolve_pepper_executable()

_resolve_pepper_executable ( )
protected
Return the absolute path to the `pepper` binary, or raise.

Definition at line 81 of file PepperConfig.py.

81def _resolve_pepper_executable():
82 """Return the absolute path to the `pepper` binary, or raise."""
83 exe = os.environ.get("PEPPER_EXECUTABLE") or shutil.which("pepper")
84 if not exe or not os.path.isfile(exe):
85 raise RuntimeError(
86 "Could not locate the `pepper` executable. "
87 "Either set PEPPER_EXECUTABLE, or ensure that an LCG view "
88 "containing pepper_kokkos is on PATH "
89 "(e.g. /cvmfs/sft.cern.ch/lcg/views/LCG_109a/x86_64-el9-gcc13-opt)."
90 )
91 return exe
92
93

◆ _split_ext()

_split_ext ( filename)
protected
Return (stem, longest matching extension) where the extension
is one of the keys in _EXT_MAP. Raises if no match.

Definition at line 118 of file PepperConfig.py.

118def _split_ext(filename):
119 """Return (stem, longest matching extension) where the extension
120 is one of the keys in _EXT_MAP. Raises if no match."""
121 for ext in sorted(_EXT_MAP.keys(), key=len, reverse=True):
122 if filename.endswith(ext):
123 return filename[:-len(ext)], ext
124 raise ValueError(
125 "Pepper output filename %r must end in one of: %s"
126 % (filename, ", ".join(sorted(_EXT_MAP.keys()))))
127
128

◆ arrange_output()

arrange_output ( events_file,
runArgs )
Hand an LHE file from run_pepper() over to the Gen_tf.py skeleton.

Mirrors MadGraphControl.arrange_output():

* the events are made available uncompressed as `<name>.events`;
* if the job has a TXT output (--outputTXTFile=<name>.tar.gz), that
  file is packed into the tarball;
* runArgs.inputGeneratorFile is set so that the skeleton finds
  `<name>.events`, links it to `events.lhe` (which Pythia8_LHEF.py
  reads) and counts its events for the job metadata.

Non-LHE outputs (.hepmc3, .hdf5, .debug) are left alone.

Parameters
----------
events_file : LHE file from run_pepper() (.lhef[.gz] or .events[.gz]).
runArgs     : The transform's runArgs.

Returns
-------
The value given to runArgs.inputGeneratorFile, or None if nothing
was arranged.

Definition at line 244 of file PepperConfig.py.

244def arrange_output(events_file, runArgs):
245 """Hand an LHE file from run_pepper() over to the Gen_tf.py skeleton.
246
247 Mirrors MadGraphControl.arrange_output():
248
249 * the events are made available uncompressed as `<name>.events`;
250 * if the job has a TXT output (--outputTXTFile=<name>.tar.gz), that
251 file is packed into the tarball;
252 * runArgs.inputGeneratorFile is set so that the skeleton finds
253 `<name>.events`, links it to `events.lhe` (which Pythia8_LHEF.py
254 reads) and counts its events for the job metadata.
255
256 Non-LHE outputs (.hepmc3, .hdf5, .debug) are left alone.
257
258 Parameters
259 ----------
260 events_file : LHE file from run_pepper() (.lhef[.gz] or .events[.gz]).
261 runArgs : The transform's runArgs.
262
263 Returns
264 -------
265 The value given to runArgs.inputGeneratorFile, or None if nothing
266 was arranged.
267 """
268 _, ext = _split_ext(events_file)
269 if _EXT_MAP[ext] not in _LHE_EXTS:
270 if getattr(runArgs, "outputTXTFile", None):
271 raise RuntimeError(
272 "A TXT output was requested but %s is not an LHE file; "
273 "use an .lhef[.gz] or .events[.gz] output" % events_file)
274 log.info("%s is not an LHE file: not handed to the transform",
275 events_file)
276 return None
277
278 output_txt = getattr(runArgs, "outputTXTFile", None)
279 output_ds = output_txt if output_txt else _TMP_LHE_NAME
280 lhe = output_ds.split(".tar.gz")[0] + ".events"
281
282 # Uncompressed <name>.events: a link if pepper's file is already
283 # plain text, an unpacked copy otherwise.
284 if os.path.realpath(lhe) != os.path.realpath(events_file):
285 if os.path.lexists(lhe):
286 os.remove(lhe)
287 if events_file.endswith(".gz"):
288 with gzip.open(events_file, "rb") as src, open(lhe, "wb") as dst:
289 shutil.copyfileobj(src, dst)
290 else:
291 os.symlink(os.path.realpath(events_file), lhe)
292
293 if output_txt:
294 with tarfile.open(output_txt, "w:gz", dereference=True) as tar:
295 tar.add(lhe, arcname=os.path.basename(lhe))
296 log.info("Packed %s into %s", lhe, output_txt)
297 # As in MadGraphControl: the skeleton derives the name of the
298 # .events file from this.
299 input_file = output_txt.split(".TXT")[0]
300 else:
301 input_file = output_ds
302
303 log.info("Setting runArgs.inputGeneratorFile to %s (events in %s)",
304 input_file, lhe)
305 runArgs.inputGeneratorFile = input_file
306 return input_file

◆ count_lhe_events()

count_lhe_events ( path)
Return the number of <event> blocks in a (possibly gzipped) LHE file.

Definition at line 136 of file PepperConfig.py.

136def count_lhe_events(path):
137 """Return the number of <event> blocks in a (possibly gzipped) LHE file."""
138 n = 0
139 with _open_events(path) as f:
140 for line in f:
141 if _EVENT_TAG.match(line):
142 n += 1
143 return n
144
145

◆ run_pepper()

run_pepper ( process,
nevents,
seed,
ecm,
output = "pepper.lhef",
batch_size = None,
extra_args = None )
Invoke `pepper`, produce an event file, and return its path.

The default output is an uncompressed LHE file. Use
arrange_output() afterwards to hand it to the Gen_tf.py skeleton.

Parameters
----------
process     : Pepper process shortcut, e.g. "ppjj", "ppz1j", "pptt".
nevents     : Minimum number of events to be written (passed to
              pepper as --n-events). Pepper stops at the first batch
              boundary after reaching it, so the file usually holds
              slightly more.
seed        : Random seed (typically runArgs.randomSeed).
ecm         : Centre-of-mass energy in GeV (typically runArgs.ecmEnergy).
output      : Output filename. Must end in one of .events[.gz],
              .lhef[.gz], .hepmc3[.gz], .hdf5, or .debug.
batch_size  : Phase-space points per batch. Throughput tuning only;
              defaults to DEFAULT_BATCH_SIZE.
extra_args  : List of additional CLI args to pass through.

Returns
-------
The absolute path of the event file in $PWD.

Definition at line 146 of file PepperConfig.py.

152 extra_args=None):
153 """Invoke `pepper`, produce an event file, and return its path.
154
155 The default output is an uncompressed LHE file. Use
156 arrange_output() afterwards to hand it to the Gen_tf.py skeleton.
157
158 Parameters
159 ----------
160 process : Pepper process shortcut, e.g. "ppjj", "ppz1j", "pptt".
161 nevents : Minimum number of events to be written (passed to
162 pepper as --n-events). Pepper stops at the first batch
163 boundary after reaching it, so the file usually holds
164 slightly more.
165 seed : Random seed (typically runArgs.randomSeed).
166 ecm : Centre-of-mass energy in GeV (typically runArgs.ecmEnergy).
167 output : Output filename. Must end in one of .events[.gz],
168 .lhef[.gz], .hepmc3[.gz], .hdf5, or .debug.
169 batch_size : Phase-space points per batch. Throughput tuning only;
170 defaults to DEFAULT_BATCH_SIZE.
171 extra_args : List of additional CLI args to pass through.
172
173 Returns
174 -------
175 The absolute path of the event file in $PWD.
176 """
177 nevents = int(nevents)
178 if nevents < 1:
179 raise ValueError("run_pepper: nevents must be >= 1, got %d" % nevents)
180
181 exe = _resolve_pepper_executable()
182 data = _resolve_data_path(exe)
183
184 stem, user_ext = _split_ext(output)
185 pepper_ext = _EXT_MAP[user_ext]
186 pepper_output = stem + pepper_ext
187
188 if batch_size is None:
189 batch_size = DEFAULT_BATCH_SIZE
190
191 cmd = [exe,
192 "--process", str(process),
193 "--collision-energy", str(ecm),
194 "--seed", str(seed),
195 "--batch-size", str(batch_size),
196 "--n-events", str(nevents),
197 "--output", pepper_output]
198 if extra_args:
199 cmd += list(extra_args)
200
201 env = os.environ.copy()
202 env["PEPPER_DATA_PATH"] = data
203 # Sensible OpenMP defaults so Kokkos doesn't warn at startup.
204 env.setdefault("OMP_PROC_BIND", "spread")
205 env.setdefault("OMP_PLACES", "threads")
206
207 # Make sure the yield check below can only ever see this run's events.
208 if os.path.lexists(pepper_output):
209 os.remove(pepper_output)
210
211 log.info("PEPPER_DATA_PATH = %s", data)
212 log.info("Pepper command: %s", " ".join(cmd))
213
214 rc = subprocess.call(cmd, env=env)
215 if rc != 0:
216 raise RuntimeError("pepper exited with rc=%d" % rc)
217 if not os.path.isfile(pepper_output):
218 raise RuntimeError("pepper succeeded but %s is missing" % pepper_output)
219
220 if pepper_ext in _LHE_EXTS:
221 nwritten = count_lhe_events(pepper_output)
222 log.info("Pepper wrote %d events to %s (%d requested)",
223 nwritten, pepper_output, nevents)
224 if nwritten < nevents:
225 raise RuntimeError(
226 "pepper wrote only %d events to %s, but %d were requested"
227 % (nwritten, pepper_output, nevents))
228 else:
229 log.info("Output format %s: number of written events not checked",
230 pepper_ext)
231
232 # If user requested a name different from what pepper produces
233 # (typical: user asked for .events.gz; pepper made .lhef.gz),
234 # link the user-facing name to the actual file.
235 if output != pepper_output:
236 if os.path.lexists(output):
237 os.remove(output)
238 os.symlink(os.path.basename(pepper_output), output)
239 log.info("Linked %s -> %s", output, pepper_output)
240
241 return os.path.abspath(output)
242
243

Variable Documentation

◆ _EVENT_TAG

PepperConfig._EVENT_TAG = re.compile(r"^\s*<event[\s>]")
protected

Definition at line 78 of file PepperConfig.py.

◆ _EXT_MAP

dict PepperConfig._EXT_MAP
protected
Initial value:
= {
".events.gz": ".lhef.gz",
".events": ".lhef",
# Pass-through: pepper recognises these natively.
".lhef.gz": ".lhef.gz",
".lhef": ".lhef",
".hepmc3.gz": ".hepmc3.gz",
".hepmc3": ".hepmc3",
".hdf5": ".hdf5",
".debug": ".debug",
}

Definition at line 52 of file PepperConfig.py.

◆ _LHE_EXTS

tuple PepperConfig._LHE_EXTS = (".lhef.gz", ".lhef")
protected

Definition at line 66 of file PepperConfig.py.

◆ _TMP_LHE_NAME

str PepperConfig._TMP_LHE_NAME = "pepper_LHE_events"
protected

Definition at line 70 of file PepperConfig.py.

◆ DEFAULT_BATCH_SIZE

int PepperConfig.DEFAULT_BATCH_SIZE = 1000

Definition at line 76 of file PepperConfig.py.

◆ log

PepperConfig.log = logging.getLogger("PepperConfig")

Definition at line 45 of file PepperConfig.py.