lheutils 0.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lheutils/__init__.py +1 -0
- lheutils/_version.py +34 -0
- lheutils/cli/lhe2lhe.py +159 -0
- lheutils/cli/lhecheck.py +619 -0
- lheutils/cli/lhediff.py +561 -0
- lheutils/cli/lhefilter.py +371 -0
- lheutils/cli/lheinfo.py +306 -0
- lheutils/cli/lhemerge.py +172 -0
- lheutils/cli/lheshow.py +118 -0
- lheutils/cli/lhesplit.py +132 -0
- lheutils/cli/lhestack.py +233 -0
- lheutils/cli/lheunstack.py +78 -0
- lheutils/cli/util.py +18 -0
- lheutils-0.0.2.dist-info/METADATA +73 -0
- lheutils-0.0.2.dist-info/RECORD +18 -0
- lheutils-0.0.2.dist-info/WHEEL +4 -0
- lheutils-0.0.2.dist-info/entry_points.txt +10 -0
- lheutils-0.0.2.dist-info/licenses/LICENSE +190 -0
lheutils/cli/lhediff.py
ADDED
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
CLI tool to compare and diff two LHE files.
|
|
4
|
+
|
|
5
|
+
This tool compares two Les Houches Event (LHE) files and reports differences
|
|
6
|
+
in their initialization sections, event counts, and optionally event contents.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import math
|
|
11
|
+
import signal
|
|
12
|
+
import sys
|
|
13
|
+
from collections.abc import Iterable
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from itertools import zip_longest
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from typing_extensions import Self
|
|
20
|
+
|
|
21
|
+
import pylhe
|
|
22
|
+
|
|
23
|
+
from lheutils.cli.util import create_base_parser
|
|
24
|
+
|
|
25
|
+
# We do not want a Python Exception on broken pipe, which happens when piping to 'head' or 'less'
|
|
26
|
+
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def diff_lhe_event_infos(
|
|
30
|
+
lheei1: pylhe.LHEEventInfo, lheei2: pylhe.LHEEventInfo
|
|
31
|
+
) -> pylhe.LHEEventInfo:
|
|
32
|
+
"""
|
|
33
|
+
Compare LHEEventInfo objects from two LHE files and report differences.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
lheei1: LHEEventInfo from first file
|
|
37
|
+
lheei2: LHEEventInfo from second file
|
|
38
|
+
"""
|
|
39
|
+
return pylhe.LHEEventInfo(
|
|
40
|
+
nparticles=lheei1.nparticles - lheei2.nparticles,
|
|
41
|
+
pid=lheei1.pid - lheei2.pid,
|
|
42
|
+
weight=lheei1.weight - lheei2.weight,
|
|
43
|
+
scale=lheei1.scale - lheei2.scale,
|
|
44
|
+
aqed=lheei1.aqed - lheei2.aqed,
|
|
45
|
+
aqcd=lheei1.aqcd - lheei2.aqcd,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def diff_lhe_particles(
|
|
50
|
+
p1: pylhe.LHEParticle, p2: pylhe.LHEParticle
|
|
51
|
+
) -> pylhe.LHEParticle:
|
|
52
|
+
"""
|
|
53
|
+
Compare two LHEParticle objects and report differences.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
p1: First LHEParticle
|
|
57
|
+
p2: Second LHEParticle
|
|
58
|
+
Returns:
|
|
59
|
+
Dictionary of differing attributes with their values
|
|
60
|
+
"""
|
|
61
|
+
return pylhe.LHEParticle(
|
|
62
|
+
id=p1.id - p2.id,
|
|
63
|
+
status=p1.status - p2.status,
|
|
64
|
+
mother1=p1.mother1 - p2.mother1,
|
|
65
|
+
mother2=p1.mother2 - p2.mother2,
|
|
66
|
+
color1=p1.color1 - p2.color1,
|
|
67
|
+
color2=p1.color2 - p2.color2,
|
|
68
|
+
px=p1.px - p2.px,
|
|
69
|
+
py=p1.py - p2.py,
|
|
70
|
+
pz=p1.pz - p2.pz,
|
|
71
|
+
e=p1.e - p2.e,
|
|
72
|
+
m=p1.m - p2.m,
|
|
73
|
+
lifetime=p1.lifetime - p2.lifetime,
|
|
74
|
+
spin=p1.spin - p2.spin,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass
|
|
79
|
+
class LHEAccumulatedDiff:
|
|
80
|
+
ndiff: int
|
|
81
|
+
|
|
82
|
+
def __add__(self, other: "LHEAccumulatedDiff") -> "LHEAccumulatedDiff":
|
|
83
|
+
"""Add two LHEAccumulatedDiff objects together."""
|
|
84
|
+
return LHEAccumulatedDiff(ndiff=self.ndiff + other.ndiff)
|
|
85
|
+
|
|
86
|
+
def __iadd__(self, other: "LHEAccumulatedDiff") -> Self:
|
|
87
|
+
"""In-place addition for LHEAccumulatedDiff objects."""
|
|
88
|
+
self.ndiff += other.ndiff
|
|
89
|
+
return self
|
|
90
|
+
|
|
91
|
+
def print(self, *args: Any, **kwargs: Any) -> None:
|
|
92
|
+
"""Print the number of differences."""
|
|
93
|
+
print(f"Total differences: {self.ndiff}", *args, **kwargs)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass
|
|
97
|
+
class LHEDiff:
|
|
98
|
+
"""Generic dataclass to store differences between old and new values."""
|
|
99
|
+
|
|
100
|
+
old: Any
|
|
101
|
+
new: Any
|
|
102
|
+
|
|
103
|
+
def print(self, *args: Any, **kwargs: Any) -> LHEAccumulatedDiff:
|
|
104
|
+
print(f"{self.old} -> {self.new}", *args, **kwargs)
|
|
105
|
+
return LHEAccumulatedDiff(ndiff=1)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@dataclass
|
|
109
|
+
class LHEInitDiff:
|
|
110
|
+
"""Dataclass to store differences in LHE initialization sections."""
|
|
111
|
+
|
|
112
|
+
diffs: dict[str, LHEDiff]
|
|
113
|
+
|
|
114
|
+
def print(self, *args: Any, end: str = "\n", **kwargs: Any) -> LHEAccumulatedDiff:
|
|
115
|
+
lhead = LHEAccumulatedDiff(ndiff=0)
|
|
116
|
+
for key, diff in self.diffs.items():
|
|
117
|
+
print(f"{key}: ", *args, end="", **kwargs)
|
|
118
|
+
lhead += diff.print(*args, end=end, **kwargs)
|
|
119
|
+
return lhead
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def diff_lhe_init(
|
|
123
|
+
lhei1: pylhe.LHEInit,
|
|
124
|
+
lhei2: pylhe.LHEInit,
|
|
125
|
+
check_init: bool,
|
|
126
|
+
check_weights: bool,
|
|
127
|
+
absolute_tolerance: float,
|
|
128
|
+
relative_tolerance: float,
|
|
129
|
+
) -> LHEInitDiff:
|
|
130
|
+
"""
|
|
131
|
+
Compare LHEInitInfo objects from two LHE files and report differences.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
lheii1: LHEInitInfo from first file
|
|
135
|
+
lheii2: LHEInitInfo from second file
|
|
136
|
+
check_init: Whether to check initialization section
|
|
137
|
+
check_weights: Whether to check weight groups and weights
|
|
138
|
+
absolute_tolerance: Absolute tolerance for numeric comparisons
|
|
139
|
+
relative_tolerance: Relative tolerance for numeric comparisons
|
|
140
|
+
"""
|
|
141
|
+
diffs = {}
|
|
142
|
+
if check_init:
|
|
143
|
+
if lhei1.initInfo.beamA != lhei2.initInfo.beamA:
|
|
144
|
+
diffs["beamA"] = LHEDiff(old=lhei1.initInfo.beamA, new=lhei2.initInfo.beamA)
|
|
145
|
+
if not math.isclose(
|
|
146
|
+
lhei1.initInfo.energyA,
|
|
147
|
+
lhei2.initInfo.energyA,
|
|
148
|
+
rel_tol=relative_tolerance,
|
|
149
|
+
abs_tol=absolute_tolerance,
|
|
150
|
+
):
|
|
151
|
+
diffs["energyA"] = LHEDiff(
|
|
152
|
+
old=lhei1.initInfo.energyA, new=lhei2.initInfo.energyA
|
|
153
|
+
)
|
|
154
|
+
if lhei1.initInfo.beamB != lhei2.initInfo.beamB:
|
|
155
|
+
diffs["beamB"] = LHEDiff(old=lhei1.initInfo.beamB, new=lhei2.initInfo.beamB)
|
|
156
|
+
if not math.isclose(
|
|
157
|
+
lhei1.initInfo.energyB,
|
|
158
|
+
lhei2.initInfo.energyB,
|
|
159
|
+
rel_tol=relative_tolerance,
|
|
160
|
+
abs_tol=absolute_tolerance,
|
|
161
|
+
):
|
|
162
|
+
diffs["energyB"] = LHEDiff(
|
|
163
|
+
old=lhei1.initInfo.energyB, new=lhei2.initInfo.energyB
|
|
164
|
+
)
|
|
165
|
+
if lhei1.initInfo.PDFgroupA != lhei2.initInfo.PDFgroupA:
|
|
166
|
+
diffs["PDFgroupA"] = LHEDiff(
|
|
167
|
+
old=lhei1.initInfo.PDFgroupA, new=lhei2.initInfo.PDFgroupA
|
|
168
|
+
)
|
|
169
|
+
if lhei1.initInfo.PDFgroupB != lhei2.initInfo.PDFgroupB:
|
|
170
|
+
diffs["PDFgroupB"] = LHEDiff(
|
|
171
|
+
old=lhei1.initInfo.PDFgroupB, new=lhei2.initInfo.PDFgroupB
|
|
172
|
+
)
|
|
173
|
+
if lhei1.initInfo.PDFsetA != lhei2.initInfo.PDFsetA:
|
|
174
|
+
diffs["PDFsetA"] = LHEDiff(
|
|
175
|
+
old=lhei1.initInfo.PDFsetA, new=lhei2.initInfo.PDFsetA
|
|
176
|
+
)
|
|
177
|
+
if lhei1.initInfo.PDFsetB != lhei2.initInfo.PDFsetB:
|
|
178
|
+
diffs["PDFsetB"] = LHEDiff(
|
|
179
|
+
old=lhei1.initInfo.PDFsetB, new=lhei2.initInfo.PDFsetB
|
|
180
|
+
)
|
|
181
|
+
if lhei1.initInfo.weightingStrategy != lhei2.initInfo.weightingStrategy:
|
|
182
|
+
diffs["weightingStrategy"] = LHEDiff(
|
|
183
|
+
old=lhei1.initInfo.weightingStrategy,
|
|
184
|
+
new=lhei2.initInfo.weightingStrategy,
|
|
185
|
+
)
|
|
186
|
+
if lhei1.initInfo.numProcesses != lhei2.initInfo.numProcesses:
|
|
187
|
+
diffs["numProcesses"] = LHEDiff(
|
|
188
|
+
old=lhei1.initInfo.numProcesses, new=lhei2.initInfo.numProcesses
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
for proc1, proc2 in zip(lhei1.procInfo, lhei2.procInfo):
|
|
192
|
+
if not math.isclose(
|
|
193
|
+
proc1.xSection,
|
|
194
|
+
proc2.xSection,
|
|
195
|
+
rel_tol=relative_tolerance,
|
|
196
|
+
abs_tol=absolute_tolerance,
|
|
197
|
+
):
|
|
198
|
+
diffs[f"process_{proc1.procId}_xSection"] = LHEDiff(
|
|
199
|
+
old=proc1.xSection, new=proc2.xSection
|
|
200
|
+
)
|
|
201
|
+
if not math.isclose(
|
|
202
|
+
proc1.error,
|
|
203
|
+
proc2.error,
|
|
204
|
+
rel_tol=relative_tolerance,
|
|
205
|
+
abs_tol=absolute_tolerance,
|
|
206
|
+
):
|
|
207
|
+
diffs[f"process_{proc1.procId}_error"] = LHEDiff(
|
|
208
|
+
old=proc1.error, new=proc2.error
|
|
209
|
+
)
|
|
210
|
+
if not math.isclose(
|
|
211
|
+
proc1.unitWeight,
|
|
212
|
+
proc2.unitWeight,
|
|
213
|
+
rel_tol=relative_tolerance,
|
|
214
|
+
abs_tol=absolute_tolerance,
|
|
215
|
+
):
|
|
216
|
+
diffs[f"process_{proc1.procId}_unitWeight"] = LHEDiff(
|
|
217
|
+
old=proc1.unitWeight, new=proc2.unitWeight
|
|
218
|
+
)
|
|
219
|
+
if proc1.procId != proc2.procId:
|
|
220
|
+
diffs[f"process_{proc1.procId}_procId"] = LHEDiff(
|
|
221
|
+
old=proc1.procId, new=proc2.procId
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
if check_weights:
|
|
225
|
+
if len(lhei1.weightgroup) != len(lhei2.weightgroup):
|
|
226
|
+
diffs["num_weight_groups"] = LHEDiff(
|
|
227
|
+
old=len(lhei1.weightgroup), new=len(lhei2.weightgroup)
|
|
228
|
+
)
|
|
229
|
+
for (nwg1, wg1), (nwg2, wg2) in zip(
|
|
230
|
+
lhei1.weightgroup.items(), lhei2.weightgroup.items()
|
|
231
|
+
):
|
|
232
|
+
if nwg1 != nwg2:
|
|
233
|
+
diffs[f"weight_group_key_{nwg1}"] = LHEDiff(old=nwg1, new=nwg2)
|
|
234
|
+
if len(wg1.attrib) != len(wg2.attrib):
|
|
235
|
+
diffs[f"weight_group_{nwg1}_num_attrib"] = LHEDiff(
|
|
236
|
+
old=len(wg1.attrib), new=len(wg2.attrib)
|
|
237
|
+
)
|
|
238
|
+
for (k1, v1), (k2, v2) in zip(wg1.attrib.items(), wg2.attrib.items()):
|
|
239
|
+
if k1 != k2:
|
|
240
|
+
diffs[f"weight_group_{nwg1}_attrib_key_{k1}"] = LHEDiff(
|
|
241
|
+
old=k1, new=k2
|
|
242
|
+
)
|
|
243
|
+
if v1 != v2:
|
|
244
|
+
diffs[f"weight_group_{nwg1}_attrib_value_{k1}"] = LHEDiff(
|
|
245
|
+
old=v1, new=v2
|
|
246
|
+
)
|
|
247
|
+
if len(wg1.weights) != len(wg2.weights):
|
|
248
|
+
diffs[f"weight_group_{nwg1}_num_weights"] = LHEDiff(
|
|
249
|
+
old=len(wg1.weights), new=len(wg2.weights)
|
|
250
|
+
)
|
|
251
|
+
for (n1, lhewi1), (n2, lhwi2) in zip(
|
|
252
|
+
wg1.weights.items(), wg2.weights.items()
|
|
253
|
+
):
|
|
254
|
+
if n1 != n2:
|
|
255
|
+
diffs[f"weight_group_{nwg1}_weight_key_{n1}"] = LHEDiff(
|
|
256
|
+
old=n1, new=n2
|
|
257
|
+
)
|
|
258
|
+
if lhewi1.name != lhwi2.name:
|
|
259
|
+
diffs[f"weight_group_{nwg1}_weight_{n1}_name"] = LHEDiff(
|
|
260
|
+
old=lhewi1.name, new=lhwi2.name
|
|
261
|
+
)
|
|
262
|
+
if lhewi1.index != lhwi2.index:
|
|
263
|
+
diffs[f"weight_group_{nwg1}_weight_{n1}_index"] = LHEDiff(
|
|
264
|
+
old=lhewi1.index, new=lhwi2.index
|
|
265
|
+
)
|
|
266
|
+
if len(lhewi1.attrib) != len(lhwi2.attrib):
|
|
267
|
+
diffs[f"weight_group_{nwg1}_weight_{n1}_num_attrib"] = LHEDiff(
|
|
268
|
+
old=len(lhewi1.attrib), new=len(lhwi2.attrib)
|
|
269
|
+
)
|
|
270
|
+
for (ak1, av1), (ak2, av2) in zip(
|
|
271
|
+
lhewi1.attrib.items(), lhwi2.attrib.items()
|
|
272
|
+
):
|
|
273
|
+
if ak1 != ak2:
|
|
274
|
+
diffs[
|
|
275
|
+
f"weight_group_{nwg1}_weight_{n1}_attrib_key_{ak1}"
|
|
276
|
+
] = LHEDiff(old=ak1, new=ak2)
|
|
277
|
+
if av1 != av2:
|
|
278
|
+
diffs[
|
|
279
|
+
f"weight_group_{nwg1}_weight_{n1}_attrib_value_{ak1}"
|
|
280
|
+
] = LHEDiff(old=av1, new=av2)
|
|
281
|
+
|
|
282
|
+
if lhei1.LHEVersion != lhei2.LHEVersion:
|
|
283
|
+
diffs["LHEVersion"] = LHEDiff(old=lhei1.LHEVersion, new=lhei2.LHEVersion)
|
|
284
|
+
|
|
285
|
+
return LHEInitDiff(diffs=diffs)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
@dataclass
|
|
289
|
+
class LHEEventDiff:
|
|
290
|
+
"""Dataclass to store differences in LHE initialization sections."""
|
|
291
|
+
|
|
292
|
+
event_index: int
|
|
293
|
+
diffs: dict[str, LHEDiff]
|
|
294
|
+
|
|
295
|
+
def print(self, *args: Any, end: str = "\n", **kwargs: Any) -> LHEAccumulatedDiff:
|
|
296
|
+
lhead = LHEAccumulatedDiff(ndiff=0)
|
|
297
|
+
for key, diff in self.diffs.items():
|
|
298
|
+
print(f"{key}: ", *args, end="", **kwargs)
|
|
299
|
+
lhead += diff.print(*args, end=end, **kwargs)
|
|
300
|
+
return lhead
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def diff_lhe_events(
|
|
304
|
+
events1: Iterable[pylhe.LHEEvent],
|
|
305
|
+
events2: Iterable[pylhe.LHEEvent],
|
|
306
|
+
check_events: bool,
|
|
307
|
+
abs_tol: float,
|
|
308
|
+
rel_tol: float,
|
|
309
|
+
) -> Iterable[LHEEventDiff]:
|
|
310
|
+
for j, (event1, event2) in enumerate(zip_longest(events1, events2), start=1):
|
|
311
|
+
diffs = {}
|
|
312
|
+
if event1 is None:
|
|
313
|
+
diffs[f"event_{j}"] = LHEDiff(old="missing", new="present")
|
|
314
|
+
yield LHEEventDiff(event_index=j, diffs=diffs)
|
|
315
|
+
continue
|
|
316
|
+
if event2 is None:
|
|
317
|
+
diffs[f"event_{j}"] = LHEDiff(old="present", new="missing")
|
|
318
|
+
yield LHEEventDiff(event_index=j, diffs=diffs)
|
|
319
|
+
continue
|
|
320
|
+
if check_events:
|
|
321
|
+
if event1.eventinfo.nparticles != event2.eventinfo.nparticles:
|
|
322
|
+
diffs[f"event_{j}_eventinfo_nparticles"] = LHEDiff(
|
|
323
|
+
old=event1.eventinfo.nparticles, new=event2.eventinfo.nparticles
|
|
324
|
+
)
|
|
325
|
+
if event1.eventinfo.pid != event2.eventinfo.pid:
|
|
326
|
+
diffs[f"event_{j}_eventinfo_pid"] = LHEDiff(
|
|
327
|
+
old=event1.eventinfo.pid, new=event2.eventinfo.pid
|
|
328
|
+
)
|
|
329
|
+
if not math.isclose(
|
|
330
|
+
event1.eventinfo.weight,
|
|
331
|
+
event2.eventinfo.weight,
|
|
332
|
+
abs_tol=abs_tol,
|
|
333
|
+
rel_tol=rel_tol,
|
|
334
|
+
):
|
|
335
|
+
diffs[f"event_{j}_eventinfo_weight"] = LHEDiff(
|
|
336
|
+
old=event1.eventinfo.weight, new=event2.eventinfo.weight
|
|
337
|
+
)
|
|
338
|
+
if not math.isclose(
|
|
339
|
+
event1.eventinfo.scale,
|
|
340
|
+
event2.eventinfo.scale,
|
|
341
|
+
abs_tol=abs_tol,
|
|
342
|
+
rel_tol=rel_tol,
|
|
343
|
+
):
|
|
344
|
+
diffs[f"event_{j}_eventinfo_scale"] = LHEDiff(
|
|
345
|
+
old=event1.eventinfo.scale, new=event2.eventinfo.scale
|
|
346
|
+
)
|
|
347
|
+
if not math.isclose(
|
|
348
|
+
event1.eventinfo.aqed,
|
|
349
|
+
event2.eventinfo.aqed,
|
|
350
|
+
abs_tol=abs_tol,
|
|
351
|
+
rel_tol=rel_tol,
|
|
352
|
+
):
|
|
353
|
+
diffs[f"event_{j}_eventinfo_aqed"] = LHEDiff(
|
|
354
|
+
old=event1.eventinfo.aqed, new=event2.eventinfo.aqed
|
|
355
|
+
)
|
|
356
|
+
if not math.isclose(
|
|
357
|
+
event1.eventinfo.aqcd,
|
|
358
|
+
event2.eventinfo.aqcd,
|
|
359
|
+
abs_tol=abs_tol,
|
|
360
|
+
rel_tol=rel_tol,
|
|
361
|
+
):
|
|
362
|
+
diffs[f"event_{j}_eventinfo_aqcd"] = LHEDiff(
|
|
363
|
+
old=event1.eventinfo.aqcd, new=event2.eventinfo.aqcd
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
if len(event1.particles) != len(event2.particles):
|
|
367
|
+
diffs[f"event_{j}_num_particles"] = LHEDiff(
|
|
368
|
+
old=len(event1.particles), new=len(event2.particles)
|
|
369
|
+
)
|
|
370
|
+
for i, (p1, p2) in enumerate(
|
|
371
|
+
zip(event1.particles, event2.particles), start=1
|
|
372
|
+
):
|
|
373
|
+
if p1.id != p2.id:
|
|
374
|
+
diffs[f"event_{j}_particle_{i}_id"] = LHEDiff(old=p1.id, new=p2.id)
|
|
375
|
+
if p1.status != p2.status:
|
|
376
|
+
diffs[f"event_{j}_particle_{i}_status"] = LHEDiff(
|
|
377
|
+
old=p1.status, new=p2.status
|
|
378
|
+
)
|
|
379
|
+
if p1.mother1 != p2.mother1:
|
|
380
|
+
diffs[f"event_{j}_particle_{i}_mother1"] = LHEDiff(
|
|
381
|
+
old=p1.mother1, new=p2.mother1
|
|
382
|
+
)
|
|
383
|
+
if p1.mother2 != p2.mother2:
|
|
384
|
+
diffs[f"event_{j}_particle_{i}_mother2"] = LHEDiff(
|
|
385
|
+
old=p1.mother2, new=p2.mother2
|
|
386
|
+
)
|
|
387
|
+
if p1.color1 != p2.color1:
|
|
388
|
+
diffs[f"event_{j}_particle_{i}_color1"] = LHEDiff(
|
|
389
|
+
old=p1.color1, new=p2.color1
|
|
390
|
+
)
|
|
391
|
+
if p1.color2 != p2.color2:
|
|
392
|
+
diffs[f"event_{j}_particle_{i}_color2"] = LHEDiff(
|
|
393
|
+
old=p1.color2, new=p2.color2
|
|
394
|
+
)
|
|
395
|
+
if not math.isclose(p1.px, p2.px, abs_tol=abs_tol, rel_tol=rel_tol):
|
|
396
|
+
diffs[f"event_{j}_particle_{i}_px"] = LHEDiff(old=p1.px, new=p2.px)
|
|
397
|
+
if not math.isclose(p1.py, p2.py, abs_tol=abs_tol, rel_tol=rel_tol):
|
|
398
|
+
diffs[f"event_{j}_particle_{i}_py"] = LHEDiff(old=p1.py, new=p2.py)
|
|
399
|
+
if not math.isclose(p1.pz, p2.pz, abs_tol=abs_tol, rel_tol=rel_tol):
|
|
400
|
+
diffs[f"event_{j}_particle_{i}_pz"] = LHEDiff(old=p1.pz, new=p2.pz)
|
|
401
|
+
if not math.isclose(p1.e, p2.e, abs_tol=abs_tol, rel_tol=rel_tol):
|
|
402
|
+
diffs[f"event_{j}_particle_{i}_e"] = LHEDiff(old=p1.e, new=p2.e)
|
|
403
|
+
if not math.isclose(p1.m, p2.m, abs_tol=abs_tol, rel_tol=rel_tol):
|
|
404
|
+
diffs[f"event_{j}_particle_{i}_m"] = LHEDiff(old=p1.m, new=p2.m)
|
|
405
|
+
if not math.isclose(
|
|
406
|
+
p1.lifetime, p2.lifetime, abs_tol=abs_tol, rel_tol=rel_tol
|
|
407
|
+
):
|
|
408
|
+
diffs[f"event_{j}_particle_{i}_lifetime"] = LHEDiff(
|
|
409
|
+
old=p1.lifetime, new=p2.lifetime
|
|
410
|
+
)
|
|
411
|
+
if not math.isclose(p1.spin, p2.spin, abs_tol=abs_tol, rel_tol=rel_tol):
|
|
412
|
+
diffs[f"event_{j}_particle_{i}_spin"] = LHEDiff(
|
|
413
|
+
old=p1.spin, new=p2.spin
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
if diffs:
|
|
417
|
+
yield LHEEventDiff(event_index=j, diffs=diffs)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
@dataclass
|
|
421
|
+
class LHEFileDiff:
|
|
422
|
+
"""Dataclass to store differences between two LHE files."""
|
|
423
|
+
|
|
424
|
+
lheinitdiff: LHEInitDiff
|
|
425
|
+
lheeventdiffs: Iterable[LHEEventDiff]
|
|
426
|
+
|
|
427
|
+
def print(self, *args: Any, **kwargs: Any) -> LHEAccumulatedDiff:
|
|
428
|
+
ret = LHEAccumulatedDiff(ndiff=0)
|
|
429
|
+
ret += self.lheinitdiff.print(*args, **kwargs)
|
|
430
|
+
for event_diff in self.lheeventdiffs:
|
|
431
|
+
ret += event_diff.print(*args, **kwargs)
|
|
432
|
+
return ret
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def diff_lhe_files(
|
|
436
|
+
file1: str,
|
|
437
|
+
file2: str,
|
|
438
|
+
init: bool = True,
|
|
439
|
+
weights: bool = True,
|
|
440
|
+
events: bool = True,
|
|
441
|
+
abs_tol: float = 0.0,
|
|
442
|
+
rel_tol: float = 0.0,
|
|
443
|
+
) -> LHEFileDiff:
|
|
444
|
+
"""
|
|
445
|
+
Compare two LHE files and report differences.
|
|
446
|
+
|
|
447
|
+
Args:
|
|
448
|
+
file1: Path to first LHE file
|
|
449
|
+
file2: Path to second LHE file
|
|
450
|
+
abs_tol: Absolute tolerance for numeric comparisons
|
|
451
|
+
rel_tol: Relative tolerance for numeric comparisons
|
|
452
|
+
"""
|
|
453
|
+
# Read initialization sections
|
|
454
|
+
lhefile1 = pylhe.LHEFile.fromfile(file1)
|
|
455
|
+
lhefile2 = pylhe.LHEFile.fromfile(file2)
|
|
456
|
+
|
|
457
|
+
lheinitdiff = diff_lhe_init(
|
|
458
|
+
lhefile1.init, lhefile2.init, init, weights, abs_tol, rel_tol
|
|
459
|
+
)
|
|
460
|
+
lheeventdiff = diff_lhe_events(
|
|
461
|
+
lhefile1.events, lhefile2.events, events, abs_tol, rel_tol
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
return LHEFileDiff(lheinitdiff, lheeventdiff)
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def main() -> None:
|
|
468
|
+
"""Main CLI function."""
|
|
469
|
+
parser = create_base_parser(
|
|
470
|
+
description="Compare and diff two LHE files",
|
|
471
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
472
|
+
epilog="""
|
|
473
|
+
Examples:
|
|
474
|
+
lhediff file1.lhe file2.lhe # Basic comparison (init + event counts)
|
|
475
|
+
lhediff file1.lhe file2.lhe --detailed # Detailed event-by-event comparison
|
|
476
|
+
lhediff file1.lhe file2.lhe --detailed -n 100 # Compare only first 100 events
|
|
477
|
+
lhediff file1.lhe file2.lhe --abs-tol 1e-10 # Allow small absolute differences
|
|
478
|
+
lhediff file1.lhe file2.lhe --rel-tol 1e-6 --abs-tol 1e-12 # Use both relative and absolute tolerance
|
|
479
|
+
lhediff original.lhe merged.lhe --detailed --rel-tol 1e-10 # Check merge with numeric tolerance
|
|
480
|
+
""",
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
parser.add_argument("file1", help="First LHE file to compare")
|
|
484
|
+
|
|
485
|
+
parser.add_argument("file2", help="Second LHE file to compare")
|
|
486
|
+
|
|
487
|
+
parser.add_argument(
|
|
488
|
+
"--abs-tol",
|
|
489
|
+
"-a",
|
|
490
|
+
type=float,
|
|
491
|
+
default=1e-6,
|
|
492
|
+
help="Absolute tolerance for numeric comparisons (default: 1e-6)",
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
parser.add_argument(
|
|
496
|
+
"--rel-tol",
|
|
497
|
+
"-r",
|
|
498
|
+
type=float,
|
|
499
|
+
default=1e-6,
|
|
500
|
+
help="Relative tolerance for numeric comparisons (default: 1e-6)",
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
parser.add_argument(
|
|
504
|
+
"--no-init",
|
|
505
|
+
"-ni",
|
|
506
|
+
action="store_true",
|
|
507
|
+
default=False,
|
|
508
|
+
help="Don't compare initialization sections (default: False)",
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
parser.add_argument(
|
|
512
|
+
"--no-events",
|
|
513
|
+
"-ne",
|
|
514
|
+
action="store_true",
|
|
515
|
+
default=False,
|
|
516
|
+
help="Don't compare events (default: False)",
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
parser.add_argument(
|
|
520
|
+
"--no-weights",
|
|
521
|
+
"-nw",
|
|
522
|
+
action="store_true",
|
|
523
|
+
default=False,
|
|
524
|
+
help="Don't compare weight groups and weights in detail (default: True)",
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
args = parser.parse_args()
|
|
528
|
+
|
|
529
|
+
# Validate arguments
|
|
530
|
+
for file_path in [args.file1, args.file2]:
|
|
531
|
+
path = Path(file_path)
|
|
532
|
+
if not path.exists():
|
|
533
|
+
print(f"Error: File '{file_path}' does not exist", file=sys.stderr)
|
|
534
|
+
sys.exit(1)
|
|
535
|
+
if not path.is_file():
|
|
536
|
+
print(f"Error: '{file_path}' is not a file", file=sys.stderr)
|
|
537
|
+
sys.exit(1)
|
|
538
|
+
|
|
539
|
+
# Compare the files
|
|
540
|
+
lhefilediff = diff_lhe_files(
|
|
541
|
+
args.file1,
|
|
542
|
+
args.file2,
|
|
543
|
+
init=not args.no_init,
|
|
544
|
+
weights=not args.no_weights,
|
|
545
|
+
events=not args.no_events,
|
|
546
|
+
abs_tol=args.abs_tol,
|
|
547
|
+
rel_tol=args.rel_tol,
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
lhead = lhefilediff.print()
|
|
551
|
+
|
|
552
|
+
if lhead.ndiff != 0:
|
|
553
|
+
print("=" * 60)
|
|
554
|
+
lhead.print()
|
|
555
|
+
|
|
556
|
+
# We terminate based on printed string being empty, since that means no differences and events can only be looped once
|
|
557
|
+
sys.exit(0 if lhead.ndiff == 0 else 1)
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
if __name__ == "__main__":
|
|
561
|
+
main()
|