Skip to content

Fix point detector scoring: activate tallies, set ParticleRay state, fix Python reader#39

Merged
GuySten merged 2 commits intoGuySten:point-detectorfrom
zxkjack123:point-detector-contrib
Apr 30, 2026
Merged

Fix point detector scoring: activate tallies, set ParticleRay state, fix Python reader#39
GuySten merged 2 commits intoGuySten:point-detectorfrom
zxkjack123:point-detector-contrib

Conversation

@zxkjack123
Copy link
Copy Markdown

Summary

This PR fixes 4 bugs that completely prevented point detector tallies from producing any results on the point-detector branch.

Bug 1: active_point_tallies / active_point_detectors never populated

setup_active_tallies() in tally.cpp had cases for VOLUME, MESH_SURFACE, SURFACE, PULSE_HEIGHT — but no case TallyType::POINT. Both active_point_tallies and active_point_detectors remained empty. All scoring hooks guarded by if (!model::active_point_tallies.empty()) in physics.cpp/source.cpp silently skipped every event.

Bug 2: Statepoint missing estimator dataset for NEXT_EVENT

The if/else chain in openmc_statepoint_write() only handled ANALOG/TRACKLENGTH/COLLISION. No estimator dataset was written for point tallies, causing the Python StatePoint reader to crash with KeyError.

Bug 3: ParticleRay position/energy state not set for filter matching

In score_point_tally_impl(), after Ray::trace(), the ParticleRay's r() was at whatever geometry exit point the ray reached — NOT at the detector position. PointFilter::get_all_bins() requires p.r() ≈ detector (line 45 check: (p.r() - pos).norm() < FP_COINCIDENT). Also, E_last() was uninitialized, so EnergyFilter::get_all_bins() read garbage.

Bug 4: Python PointFilter missing from_hdf5 and get_pandas_dataframe

Base class from_hdf5() passed a flat numpy array to PointFilter.__init__() which expects Sequence[tuple]TypeError. Base get_pandas_dataframe() called np.repeat() on nested tuples → ValueError.

Testing

  • Water sphere smoke test: 2 point detectors × 4 energy bins → all bins non-zero ✅
  • Symmetry test: 3 equidistant detectors along x/y/z produce equal flux within 3σ ✅
  • Multi-tally test: separate flux and total tallies both score correctly ✅
  • StatePoint round-trip: estimator type, filter bins, pandas DataFrame all preserved ✅
  • 1/r² physics validation: 4 detectors at 10/20/40/80 cm in low-density water → flux ratios 3.94–3.97 vs theoretical 4.00 (< 2% deviation) ✅

Files changed

  • src/tallies/tally.cpp — Add TallyType::POINT case + clear() calls + include
  • include/openmc/tallies/tally_scoring.h — Set r(), r_last(), E_last() on ParticleRay
  • src/state_point.cpp — Write next-event estimator string
  • openmc/filter.py — Add PointFilter.from_hdf5() and get_pandas_dataframe()
  • openmc/tallies.py — Add next-event to ESTIMATOR_TYPES

Three bugs prevented point detector tallies from producing any results:

1. setup_active_tallies() had no case for TallyType::POINT, so
   active_point_tallies and active_point_detectors were never populated.
   All scoring hooks guarded by 'if (!active_point_tallies.empty())'
   silently skipped every event.

2. score_point_tally_impl() did not set ParticleRay position/energy
   state before calling score_tracklength_tally_general(). PointFilter
   requires p.r() == detector position and EnergyFilter reads p.E_last(),
   both of which were uninitialized/wrong after Ray::trace().

3. openmc_statepoint_write() had no branch for TallyEstimator::NEXT_EVENT,
   so the 'estimator' dataset was missing from the HDF5 output, causing
   Python StatePoint reader to crash with KeyError.

Fixes:
- Add TallyType::POINT case in setup_active_tallies() that pushes to
  active_point_tallies and inserts detector positions from PointFilter.
- Add corresponding clear() calls in setup_active_tallies() and
  free_memory_tally().
- Include filter_point.h in tally.cpp.
- Set p.r(), p.r_last(), and p.E_last() on the ParticleRay before scoring.
- Write 'next-event' estimator string in statepoint for NEXT_EVENT tallies.

Verified with smoke tests: water sphere with point detectors produces
non-zero flux, 1/r^2 scaling matches analytical expectation within 2%,
and symmetric detectors yield statistically consistent results.
…type

PointFilter lacked from_hdf5() and get_pandas_dataframe() overrides.
The base Filter.from_hdf5() passed a flat numpy array to PointFilter.__init__
which expects Sequence[tuple], causing TypeError. The base
get_pandas_dataframe() used np.repeat on the nested tuple structure,
causing ValueError.

Also add 'next-event' to ESTIMATOR_TYPES so the Tally.estimator setter
accepts the value read from statepoint files.

Fixes:
- Add PointFilter.from_hdf5() that reconstructs (pos, r0) tuples from
  flat HDF5 bins array.
- Add PointFilter.get_pandas_dataframe() that formats detector positions
  as readable string labels.
- Add 'next-event' to ESTIMATOR_TYPES set in tallies.py.
@zxkjack123
Copy link
Copy Markdown
Author

Hi @GuySten, just checking in — do you have a chance to review this PR? The fixes here are needed to get point detector tallies scoring correctly (currently all results are zero without these changes). Happy to discuss or adjust anything. Thanks!

@GuySten
Copy link
Copy Markdown
Owner

GuySten commented Apr 18, 2026

Currently I do not have sufficient time to dive into the point detector feature.
Your changes do make sense to me so I will probably merge them when I will have enough time.

@GuySten GuySten merged commit b061c20 into GuySten:point-detector Apr 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants