ATLAS Offline Software
Toggle main menu visibility
Loading...
Searching...
No Matches
Generators
EvgenJobTransforms
python
EvgenHelpers.py
Go to the documentation of this file.
1
# Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
2
3
import
os
4
import
re
5
import
gzip
6
import
shutil
7
import
tarfile
8
9
from
AthenaCommon.Logging
import
logging
10
11
evgenLog = logging.getLogger(
"Gen_tf"
)
12
13
14
def
_mk_symlink
(srcfile, dstfile):
15
"""Helper function to make symlinks."""
16
if
dstfile:
17
if
os.path.exists(dstfile)
and
not
os.path.samefile(dstfile, srcfile):
18
os.remove(dstfile)
19
if
not
os.path.exists(dstfile):
20
evgenLog.info(f
"Symlinking {srcfile} to {dstfile}"
)
21
print
(f
"Symlinking {srcfile} to {dstfile}"
)
22
os.symlink(srcfile, dstfile)
23
else
:
24
evgenLog.debug(f
"Symlinking: {dstfile} is already the same as {srcfile}"
)
25
26
27
def
_count_lhe_events
(lhe_file):
28
"""Helper function to count LHE events in a file.
29
Support for plain text, gz, tar.gz, tgz files.
30
Use chunked reading to avoid memory issues with large files.
31
"""
32
def
_count_in_stream(stream):
33
count_ev = 0
34
for
chunk
in
iter(
lambda
: stream.read(1024 * 1024), b
""
):
35
count_ev += chunk.count(b
"/event"
)
36
return
count_ev
37
38
if
lhe_file.endswith((
".tar.gz"
,
".tgz"
,
".tar"
)):
39
count_ev = 0
40
with
tarfile.open(lhe_file,
"r:*"
)
as
tar:
41
for
member
in
tar:
42
if
not
member.isfile():
43
continue
44
extracted = tar.extractfile(member)
45
if
extracted
is
None
:
46
continue
47
with
extracted:
48
count_ev += _count_in_stream(extracted)
49
return
count_ev
50
51
if
lhe_file.endswith(
".gz"
):
52
with
gzip.open(lhe_file,
"rb"
)
as
f:
53
return
_count_in_stream(f)
54
55
with
open(lhe_file,
"rb"
)
as
f:
56
return
_count_in_stream(f)
57
58
59
def
_find_unique_file
(pattern):
60
"""Helper functions for finding input file"""
61
import
glob
62
files = glob.glob(pattern)
63
# Check that there is exactly 1 match
64
if
not
files:
65
raise
RuntimeError(f
"No {pattern} file found"
)
66
elif
len(files) > 1:
67
raise
RuntimeError(f
"More than one {pattern} file found"
)
68
return
files[0]
69
70
71
def
_prepare_lhe_file
(input_file, output_file):
72
"""
73
Helper function to prepare LHE file for shower.
74
If the requested output file is uncompressed,
75
make a symlink to the input file.
76
If the requested output file is compressed,
77
compress the input file to the output file..
78
"""
79
if
not
output_file.endswith(
".gz"
):
80
_mk_symlink
(input_file, output_file)
81
return
82
83
if
os.path.lexists(output_file):
84
os.remove(output_file)
85
evgenLog.info(
"Compressing %s to %s"
, input_file, output_file)
86
with
open(input_file,
"rb"
)
as
source:
87
with
gzip.open(output_file,
"wb"
)
as
destination:
88
shutil.copyfileobj(source, destination)
89
90
91
def
_merge_lhe_files
(listOfFiles, outputFile):
92
"""
93
This function merges a list of input LHE files into one output file.
94
The header is taken from the first file, but the number of events is
95
updated to equal the total number of events in all input files.
96
"""
97
if
os.path.exists(outputFile):
98
print
(
"outputFile"
, outputFile,
"already exists. Will rename to"
, outputFile +
".OLD"
)
99
os.rename(outputFile, outputFile +
".OLD"
)
100
101
total_events = 0
102
for
file
in
listOfFiles:
103
total_events +=
_count_lhe_events
(file)
104
105
wrote_header =
False
106
# Produce a compressed merged file when avoidExtracting is True
107
output_opener = gzip.open
if
outputFile.endswith(
".gz"
)
else
open
108
with
output_opener(outputFile,
"wt"
)
as
output:
109
for
file
in
listOfFiles:
110
inHeader =
True
111
header =
""
112
print
(
"*** Starting file"
, file)
113
with
open(file,
"r"
)
as
infile:
114
for
line
in
infile:
115
# Reading first event signals that we are done with all header information.
116
if
"<event"
in
line
and
inHeader:
117
inHeader =
False
118
if
not
wrote_header:
119
wrote_header =
True
120
output.write(header)
121
output.write(line)
122
# Each input file ends with "</LesHouchesEvents>". We only write it once at the end.
123
elif
not
inHeader
and
"</LesHouchesEvents>"
not
in
line:
124
output.write(line)
125
126
if
inHeader:
127
# Format for storing number of events differs in MG and Powheg.
128
if
"nevents"
in
line:
129
# MG5 format is "n = nevents".
130
parts = line.split(
"="
)
131
if
parts:
132
line = line.replace(parts[0], str(total_events), 1)
133
elif
"numevts"
in
line:
134
# Powheg format is "numevts n".
135
parts = line.split()
136
if
len(parts) > 1:
137
line = line.replace(parts[1], str(total_events), 1)
138
header += line
139
140
output.write(
"</LesHouchesEvents>\n"
)
141
142
143
def
_handle_input_files
(generators, flags):
144
"""Helper for handling input files"""
145
from
GeneratorConfig.GenConfigHelpers
import
gens_lhef
146
is_lhe_input = gens_lhef(generators)
147
148
# Name of event files produced by various generators.
149
events_file_map = {
150
"Alpgen"
:
"alpgen.unw_events"
,
151
"Protos"
:
"protos.events"
,
152
"ProtosLHEF"
:
"protoslhef.events"
,
153
"BeamHaloGenerator"
:
"beamhalogen.events"
,
154
"HepMCAscii"
:
"events.hepmc"
,
155
"ReadMcAscii"
:
"events.hepmc"
,
156
}
157
eventsFile =
None
158
for
gen_name, out_file
in
events_file_map.items():
159
if
gen_name
in
generators:
160
eventsFile = out_file
161
break
162
if
eventsFile
is
None
:
163
if
is_lhe_input:
164
eventsFile =
"events.lhe.gz"
if
flags.Generator.avoidExtracting
else
"events.lhe"
165
else
:
166
raise
RuntimeError(f
"Unknown type of ME generator: {generators}"
)
167
168
genInputFiles = [f.strip()
for
f
in
flags.Generator.inputGeneratorFile.split(
","
)
if
f.strip()]
169
if
not
genInputFiles:
170
raise
RuntimeError(
"Generator.inputGeneratorFile is empty while input handling is requested"
)
171
172
def
_input_root(path, keep_suffix_after_underscore=False):
173
fname = os.path.basename(path)
174
if
any(ext
in
fname
for
ext
in
(
".tar."
,
".tgz"
,
".gz"
)):
175
fname = re.split(
r"\.tar\.|\.tgz|\.gz"
, fname, maxsplit=1)[0]
176
if
fname.endswith(
".events"
):
177
fname = fname[:-7]
178
parts = fname.split(
"._"
, 1)
179
if
keep_suffix_after_underscore
and
len(parts) > 1:
180
return
parts[0] +
"._"
+ parts[1].
split
(
"."
, 1)[0]
181
return
parts[0]
182
183
# If there is a single file, make a symlink. If multiple files, merge them into one output eventsFile.
184
if
len(genInputFiles) == 1:
185
inputroot = _input_root(genInputFiles[0], keep_suffix_after_underscore=
False
)
186
realEventsFile =
_find_unique_file
(f
"*{inputroot}.*ev*ts"
)
187
if
is_lhe_input:
188
# Compress or symlink the extracted input according to the flag.
189
_prepare_lhe_file
(realEventsFile, eventsFile)
190
return
_count_lhe_events
(eventsFile)
191
_mk_symlink
(realEventsFile, eventsFile)
192
return
None
193
194
allFiles = []
195
for
file
in
genInputFiles:
196
# Since we can have multiple files from the same task, include more of the filename
197
# to make the lookup unique in the plain-file case.
198
inputroot = _input_root(file, keep_suffix_after_underscore=
True
)
199
evgenLog.info(
"inputroot = %s"
, inputroot)
200
realEventsFile =
_find_unique_file
(f
"*{inputroot}.*ev*ts"
)
201
# The only input format where merging is permitted is LHE.
202
with
open(realEventsFile,
"r"
)
as
f:
203
first_line = f.readline()
204
if
"LesHouches"
not
in
first_line:
205
raise
RuntimeError(f
"{realEventsFile} is NOT a LesHouches file"
)
206
allFiles.append(realEventsFile)
207
_merge_lhe_files
(allFiles, eventsFile)
208
209
return
_count_lhe_events
(eventsFile)
210
211
212
def
_validate_sample_properties
(sample):
213
"""Helper function to validate and set sample properties"""
214
# Required fields with lightweight, explicit validators.
215
required_rules = {
216
"keywords"
:
lambda
v: isinstance(v, list)
and
len(v) > 0,
217
"contact"
:
lambda
v: isinstance(v, list)
and
len(v) > 0,
218
"nEventsPerJob"
:
lambda
v: v
is
not
None
,
219
}
220
for
field, validator
in
required_rules.items():
221
value = getattr(sample, field,
None
)
222
if
not
validator(value):
223
raise
RuntimeError(f
"self.{field} should be set in Sample(EvgenConfig)"
)
224
225
input_files_per_job = getattr(sample,
"inputFilesPerJob"
, 0)
226
me_generator = getattr(sample,
"MEgenerator"
,
None
)
227
228
if
input_files_per_job < 0:
229
raise
RuntimeError(
"self.inputFilesPerJob should be >= 0 in Sample(EvgenConfig)"
)
230
if
input_files_per_job > 0
and
not
me_generator:
231
raise
RuntimeError(
"self.MEgenerator should be set when self.inputFilesPerJob > 0 in Sample(EvgenConfig)"
)
232
if
input_files_per_job == 0
and
me_generator:
233
raise
RuntimeError(
"self.MEgenerator should be empty when self.inputFilesPerJob == 0 in Sample(EvgenConfig)"
)
234
235
236
def
_is_txt_only_run
(flags):
237
"""Helper function to determine if this is LHE-only generation with no showering)"""
238
has_txt = bool(flags.Output.TXTFileName)
239
has_evnt = bool(flags.Output.EVNTFileName)
240
has_yoda =bool(flags.Generator.outputYODAFile)
241
242
return
has_txt
and
not
has_evnt
and
not
has_yoda
print
void print(char *figname, TCanvas *c1)
Definition
TRTCalib_StrawStatusPlots.cxx:26
split
std::vector< std::string > split(const std::string &s, const std::string &t=":")
Definition
hcg.cxx:179
python.EvgenHelpers._is_txt_only_run
_is_txt_only_run(flags)
Definition
EvgenHelpers.py:236
python.EvgenHelpers._merge_lhe_files
_merge_lhe_files(listOfFiles, outputFile)
Definition
EvgenHelpers.py:91
python.EvgenHelpers._prepare_lhe_file
_prepare_lhe_file(input_file, output_file)
Definition
EvgenHelpers.py:71
python.EvgenHelpers._find_unique_file
_find_unique_file(pattern)
Definition
EvgenHelpers.py:59
python.EvgenHelpers._handle_input_files
_handle_input_files(generators, flags)
Definition
EvgenHelpers.py:143
python.EvgenHelpers._count_lhe_events
_count_lhe_events(lhe_file)
Definition
EvgenHelpers.py:27
python.EvgenHelpers._mk_symlink
_mk_symlink(srcfile, dstfile)
Definition
EvgenHelpers.py:14
python.EvgenHelpers._validate_sample_properties
_validate_sample_properties(sample)
Definition
EvgenHelpers.py:212
Generated on
for ATLAS Offline Software by
1.17.0