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.
@@ -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()