ATLAS Offline Software
Loading...
Searching...
No Matches
PepperConfig.py
Go to the documentation of this file.
1# Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
2"""Driver that invokes the LCG-installed `pepper` binary from inside an
3AthGeneration job.
4
5This module exists because the pepper_kokkos package as shipped in LCG
6needs three things wired up correctly before it will run:
7
81. The PEPPER_DATA_PATH env var must point at the process-data
9 directory (the binary's compiled-in path is the build server's
10 scratch dir and doesn't exist on cvmfs). The wrapper sets it
11 automatically.
12
132. The output filename's extension governs the format. The pepper
14 binary accepts `.lhef[.gz]`, `.hepmc3[.gz]`, `.hdf5`, and `.debug`.
15 AthGeneration's Gen_tf.py post-job validator globs for `*.events*`
16 (MadGraph/Powheg convention). We resolve the tension by accepting
17 `.events.gz` (or `.events`) as the user-facing default: pepper is
18 invoked with the equivalent `.lhef[.gz]` form, then symlinked back
19 to the requested `.events.gz` name.
20
213. The binary deadlocks in Kokkos initialisation on CPU-only nodes,
22 waiting for a CUDA device. Jobs MUST run on a GPU-equipped worker.
23"""
24
25import os
26import shutil
27import subprocess
28
29from AthenaCommon.Logging import logging
30
31log = logging.getLogger("PepperConfig")
32
33
34# Map of user-facing output extensions to the pepper-CLI extension
35# we actually invoke pepper with. .events[.gz] is the transform's
36# expected pattern; pepper itself doesn't recognise it, so we
37# transparently swap to .lhef[.gz] and symlink afterwards.
38_EXT_MAP = {
39 ".events.gz": ".lhef.gz",
40 ".events": ".lhef",
41 # Pass-through: pepper recognises these natively.
42 ".lhef.gz": ".lhef.gz",
43 ".lhef": ".lhef",
44 ".hepmc3.gz": ".hepmc3.gz",
45 ".hepmc3": ".hepmc3",
46 ".hdf5": ".hdf5",
47 ".debug": ".debug",
48}
49
50
52 """Return the absolute path to the `pepper` binary, or raise."""
53 exe = os.environ.get("PEPPER_EXECUTABLE") or shutil.which("pepper")
54 if not exe or not os.path.isfile(exe):
55 raise RuntimeError(
56 "Could not locate the `pepper` executable. "
57 "Either set PEPPER_EXECUTABLE, or ensure that an LCG view "
58 "containing pepper_kokkos is on PATH "
59 "(e.g. /cvmfs/sft.cern.ch/lcg/views/LCG_109a/x86_64-el9-gcc13-opt)."
60 )
61 return exe
62
63
65 """Return the directory containing the Pepper process CSVs.
66
67 Tries (in order): PEPPER_DATA_PATH env var; the share/pepper/data
68 directory next to the binary (1.8.x layout); the share/pepper_data
69 directory next to the binary (1.1.x layout).
70 """
71 candidate = os.environ.get("PEPPER_DATA_PATH")
72 if candidate and os.path.isfile(os.path.join(candidate, "2j.csv")):
73 return candidate
74
75 install_root = os.path.dirname(os.path.dirname(os.path.realpath(exe)))
76 for sub in ("share/pepper/data", "share/pepper_data"):
77 candidate = os.path.join(install_root, sub)
78 if os.path.isfile(os.path.join(candidate, "2j.csv")):
79 return candidate
80
81 raise RuntimeError(
82 "Could not locate Pepper's process-data directory. "
83 "Set PEPPER_DATA_PATH explicitly to the directory containing "
84 "2j.csv, z0j.csv, etc."
85 )
86
87
88def _split_ext(filename):
89 """Return (stem, longest matching extension) where the extension
90 is one of the keys in _EXT_MAP. Raises if no match."""
91 for ext in sorted(_EXT_MAP.keys(), key=len, reverse=True):
92 if filename.endswith(ext):
93 return filename[:-len(ext)], ext
94 raise ValueError(
95 "Pepper output filename %r must end in one of: %s"
96 % (filename, ", ".join(sorted(_EXT_MAP.keys()))))
97
98
99def run_pepper(process,
100 nevents,
101 seed,
102 ecm,
103 output="pepper.events.gz",
104 batch_size=None,
105 n_batches=None,
106 extra_args=None):
107 """Invoke `pepper`, produce an event file, and return its path.
108
109 The default output is `.events.gz` to match the convention used by
110 AthGeneration's `Gen_tf.py` for upstream-generator input files
111 (`Pythia8_LHEF` and the transform's post-job validator both look
112 for `*.events*`). Internally we invoke pepper with the
113 corresponding `.lhef[.gz]` extension and create the `.events[.gz]`
114 name as a symlink.
115
116 Parameters
117 ----------
118 process : Pepper process shortcut, e.g. "ppjj", "ppz1j", "pptt".
119 nevents : Approximate number of unweighted events wanted.
120 seed : Random seed (typically runArgs.randomSeed).
121 ecm : Centre-of-mass energy in GeV (typically runArgs.ecmEnergy).
122 output : Output filename. Must end in one of .events[.gz],
123 .lhef[.gz], .hepmc3[.gz], .hdf5, or .debug.
124 batch_size : Pepper batch size. Defaults to min(nevents, 5000).
125 n_batches : Number of batches. Defaults so that we usually
126 overshoot the requested nevents after accounting for
127 unweighting efficiency.
128 extra_args : List of additional CLI args to pass through.
129
130 Returns
131 -------
132 The absolute path of the event file in $PWD.
133 """
135 data = _resolve_data_path(exe)
136
137 stem, user_ext = _split_ext(output)
138 pepper_ext = _EXT_MAP[user_ext]
139 pepper_output = stem + pepper_ext
140
141 if batch_size is None:
142 batch_size = min(nevents, 5000)
143 if n_batches is None:
144 # Unweighting efficiency is process-dependent; pad by 2x so
145 # we usually overshoot rather than undershoot.
146 n_batches = max(1, (2 * nevents + batch_size - 1) // batch_size)
147
148 cmd = [exe,
149 "--process", str(process),
150 "--collision-energy", str(ecm),
151 "--seed", str(seed),
152 "--batch-size", str(batch_size),
153 "--n-batches", str(n_batches),
154 "--output", pepper_output]
155 if extra_args:
156 cmd += list(extra_args)
157
158 env = os.environ.copy()
159 env["PEPPER_DATA_PATH"] = data
160 # Sensible OpenMP defaults so Kokkos doesn't warn at startup.
161 env.setdefault("OMP_PROC_BIND", "spread")
162 env.setdefault("OMP_PLACES", "threads")
163
164 log.info("PEPPER_DATA_PATH = %s", data)
165 log.info("Pepper command: %s", " ".join(cmd))
166
167 rc = subprocess.call(cmd, env=env)
168 if rc != 0:
169 raise RuntimeError("pepper exited with rc=%d" % rc)
170 if not os.path.isfile(pepper_output):
171 raise RuntimeError("pepper succeeded but %s is missing" % pepper_output)
172
173 # If user requested a name different from what pepper produces
174 # (typical: user asked for .events.gz; pepper made .lhef.gz),
175 # link the user-facing name to the actual file.
176 if output != pepper_output:
177 if os.path.lexists(output):
178 os.remove(output)
179 os.symlink(os.path.basename(pepper_output), output)
180 log.info("Linked %s -> %s", output, pepper_output)
181
182 return os.path.abspath(output)
#define min(a, b)
Definition cfImp.cxx:40
#define max(a, b)
Definition cfImp.cxx:41
_resolve_data_path(exe)
run_pepper(process, nevents, seed, ecm, output="pepper.events.gz", batch_size=None, n_batches=None, extra_args=None)
_split_ext(filename)
_resolve_pepper_executable()