Skip to content

Commit f8d9bfe

Browse files
authored
Fixes last event not read issue (#64)
Reported by Pim Herbschleb via email. The HepMC3 C++ has a bug, because under some circumstances it reports reading an event as success (with read_event()), even if the input stream did not contain any event data. I am using a workaround for this, which unfortunately require the use of heuristics that can fail. Pim reported an input file on which the previous heuristic failed, so I changed the code once again to pass this and the previous tests. Some tests needed to be modified. The new code is not able to distinguish between an EOF error at the end of an event and a reading error in the middle of an event anymore, but it is more important to read events correctly which have been correctly written than handling a rare error cases more elegantly.
1 parent ba2c4c7 commit f8d9bfe

File tree

4 files changed

+2851
-116
lines changed

4 files changed

+2851
-116
lines changed

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pyhepmc is a hand-crafted mapping of C++ code to Python, `see documentation for
3434
- An alternative Numpy API accelerates event processing up to **70x** compared to the standard API.
3535
- The public API is fully documented with Python docstrings.
3636
- Objects are inspectable in Jupyter notebooks (have useful ``repr`` strings).
37-
- Events render as graphs in Jupyter notebooks (see next item).
37+
- Events render as graphs in Jupyter notebooks (see next item).
3838

3939
**pyhepmc supports visualizations powered by graphviz**
4040

src/pyhepmc/io.py

Lines changed: 61 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
from __future__ import annotations
1313
from ._core import (
1414
GenEvent,
15-
ReaderAscii,
16-
ReaderAsciiHepMC2,
17-
ReaderLHEF,
18-
ReaderHEPEVT,
15+
ReaderAscii as ReaderAsciiBase,
16+
ReaderAsciiHepMC2 as ReaderAsciiHepMC2Base,
17+
ReaderLHEF as ReaderLHEFBase,
18+
ReaderHEPEVT as ReaderHEPEVTBase,
1919
WriterAscii,
2020
WriterAsciiHepMC2,
2121
WriterHEPEVT,
@@ -38,25 +38,6 @@
3838
]
3939

4040

41-
class _Iter:
42-
def __init__(self, parent: Any):
43-
self.parent = parent
44-
45-
def __next__(self) -> GenEvent:
46-
evt = GenEvent()
47-
success = self.parent.read_event(evt)
48-
if self.parent.failed():
49-
if success: # indicates EOF
50-
raise StopIteration
51-
raise IOError("error reading event")
52-
return evt
53-
54-
def __iter__(self) -> "_Iter":
55-
return self
56-
57-
next = __next__
58-
59-
6041
def _enter(self: Any) -> Any:
6142
return self
6243

@@ -71,49 +52,68 @@ def _exit_flush(self: Any, type: Exception, value: str, tb: Any) -> bool:
7152
return False
7253

7354

74-
def _iter(self: Any) -> _Iter:
75-
return _Iter(self)
76-
77-
78-
def _read(self: Any) -> Optional[GenEvent]:
79-
evt = GenEvent()
80-
success = self.read_event(evt)
81-
if self.failed():
82-
if success: # indicates EOF
83-
return None
84-
raise IOError("error reading event")
85-
return evt if success else None
86-
87-
8855
def _read_event_lhef_patch(self: Any, evt: GenEvent) -> bool:
8956
failed = self._read_event_unpatched(evt)
9057
if failed and self.failed(): # probably EOF
9158
return True
9259
return not failed
9360

9461

62+
class _Iter:
63+
def __init__(self, reader: Any):
64+
self.reader = reader
65+
66+
def __next__(self) -> GenEvent:
67+
evt = self.reader.read()
68+
if evt is None:
69+
raise StopIteration
70+
return evt
71+
72+
def __iter__(self) -> "_Iter":
73+
return self
74+
75+
next = __next__
76+
77+
78+
class ReaderMixin:
79+
def read(self) -> Optional[GenEvent]:
80+
assert hasattr(self, "failed")
81+
assert hasattr(self, "read_event")
82+
if self.failed():
83+
# usually this means EOF was reached previously
84+
return None
85+
evt = GenEvent()
86+
success = self.read_event(evt)
87+
# workaround for bug in HepMC3, which reports success even if
88+
# the next section of the file does not contain any event data
89+
if len(evt.particles) == 0:
90+
success = False
91+
return evt if success else None
92+
93+
def __iter__(self: Any) -> _Iter:
94+
return _Iter(self)
95+
96+
__enter__ = _enter
97+
__exit__ = _exit_close
98+
99+
95100
# add contextmanager interface to IO classes
96-
ReaderAscii.__enter__ = _enter
97-
ReaderAscii.__exit__ = _exit_close
98-
ReaderAscii.__iter__ = _iter
99-
ReaderAscii.read = _read
100-
101-
ReaderAsciiHepMC2.__enter__ = _enter
102-
ReaderAsciiHepMC2.__exit__ = _exit_close
103-
ReaderAsciiHepMC2.__iter__ = _iter
104-
ReaderAsciiHepMC2.read = _read
105-
106-
ReaderLHEF.__enter__ = _enter
107-
ReaderLHEF.__exit__ = _exit_close
108-
ReaderLHEF.__iter__ = _iter
109-
ReaderLHEF._read_event_unpatched = ReaderLHEF.read_event
110-
ReaderLHEF.read_event = _read_event_lhef_patch
111-
ReaderLHEF.read = _read
112-
113-
ReaderHEPEVT.__enter__ = _enter
114-
ReaderHEPEVT.__exit__ = _exit_close
115-
ReaderHEPEVT.__iter__ = _iter
116-
ReaderHEPEVT.read = _read
101+
class ReaderAscii(ReaderAsciiBase, ReaderMixin): # type:ignore
102+
pass
103+
104+
105+
class ReaderAsciiHepMC2(ReaderAsciiHepMC2Base, ReaderMixin): # type:ignore
106+
pass
107+
108+
109+
class ReaderLHEF(ReaderLHEFBase, ReaderMixin): # type:ignore
110+
_read_event_unpatched = ReaderLHEFBase.read_event
111+
read_event = _read_event_lhef_patch
112+
113+
114+
class ReaderHEPEVT(ReaderHEPEVTBase, ReaderMixin): # type:ignore
115+
pass
116+
117117

118118
WriterAscii.__enter__ = _enter
119119
WriterAscii.__exit__ = _exit_close
@@ -256,6 +256,7 @@ def open_file() -> Any:
256256
self._file = open_file()
257257
self._ios = pyiostream(self._file)
258258

259+
Reader: Optional[Any] = None
259260
if format is None:
260261
# auto-detect
261262
if not self._file.seekable():
@@ -305,9 +306,7 @@ def open_file() -> Any:
305306
else:
306307
raise ValueError(f"mode must be 'r' or 'w', got {mode!r}")
307308

308-
def __enter__(self) -> HepMCFile:
309-
return self
310-
309+
__enter__ = _enter
311310
__exit__ = _exit_close
312311

313312
def __iter__(self) -> Any:
@@ -331,7 +330,7 @@ def write(self, event: GenEvent) -> None:
331330

332331
def close(self) -> None:
333332
if self._reader:
334-
self._reader.close()
333+
self._reader.close() # type:ignore
335334
if self._writer:
336335
self._writer.close()
337336
self._ios.flush()

0 commit comments

Comments
 (0)