Skip to content

MG mode: density_mult applied to total/absorption tally scores but not fission/nu-fission/scatter #4070

Description

@dallonby

Bug Description

In multigroup mode, Mgxs::calculate_xs multiplies the cached macro cross sections by the particle's cell density multiplier (src/mgxs.cpp, end of calculate_xs):

p.macro_xs().total = xs[temperature].total(angle, p.g()) * p.density_mult();
p.macro_xs().absorption = xs[temperature].absorption(angle, p.g()) * p.density_mult();
p.macro_xs().nu_fission = ... * p.density_mult();

Non-analog SCORE_TOTAL and SCORE_ABSORPTION score from that cache, so they include the multiplier. But non-analog SCORE_FISSION, SCORE_NU_FISSION, and SCORE_SCATTER go through Mgxs::get_xs(...) (src/tallies/tally_scoring.cpp, MG branches), which reads the raw XsData tensors and never applies density_mult.

Impact

For any MG cell with a density multiplier ≠ 1: transport itself is self-consistent (it uses the multiplied macros), so k is fine — but within one tally, total/absorption/flux-weighted scores and fission/nu-fission/scatter scores are computed at two different densities. The error on the affected scores is a silent flat factor equal to the multiplier. Default multipliers are 1, so standard problems don't see it.

Proposed resolution

This looks like a one-line-per-site fix, but the right location is a design choice — either

  1. apply p.density_mult() inside the MG non-analog scoring paths of tally_scoring.cpp (keeps get_xs a pure data accessor), or
  2. thread the multiplier through Mgxs::get_xs,

so I've filed it as an issue rather than guessing in a PR. Happy to submit whichever variant is preferred.

Found while porting the transport engine to Apple Silicon GPUs (dallonby/openmc-metal) — the port's GPU tally scores initially applied the multiplier uniformly and disagreed with the CPU reference on exactly these three scores, which led to auditing the asymmetry. (Related: #4067, #4068 from the same audit.)

🤖 Generated with Claude Code

Activity

  1. kvnloo commented on Sep 25, 2026

    @kvnloo
    Contributor

    I traced this independently and I think there's a useful invariant we could use for a focused regression.

    For a simple one-group, non-fissile material, could we test

    total = absorption + scattering

    inside the same track-length tally while varying the cell density multiplier, e.g. 0.5, 1.0, and 2.0?

    That seems useful because all three scores would be evaluated from the same particle histories, so we're testing internal physical consistency rather than comparing noisy results from separate simulations.

    From the current source, my understanding is that total and absorption reach the density-scaled particle cache, while macroscopic scatter reaches the underlying MGXS data directly. If that's correct, the closure relation should expose the mismatch very cleanly.

    Would this be a useful regression to establish before deciding whether the multiplier belongs in the tally-scoring paths or lower in the MGXS accessor?

    I prepared the synthetic one-group case and checked its expected algebra, but haven't executed it against a native OpenMC build yet.

    AI-assisted source review and test preparation.

  2. kvnloo commented on Sep 25, 2026

    @kvnloo
    Contributor

    Following up with a native reproduction at production source 1d75981, using synthetic, non-fissile one-group data.

    Could this same-history closure check be a useful regression for the scattering side of the issue?

    The track-length scores divided by flux are:

    Density multiplier total absorption scatter nu-scatter
    1.0 1.0 0.5 0.5 0.5
    0.5 0.5 0.25 0.5 0.5
    2.0 2.0 1.0 0.5 0.5

    The unit-multiplier control satisfies total = absorption + scatter; the other two have relative closure discrepancies of 0.5 and 0.25.

    After correcting my probe setup, the final case uses a unit atom/b-cm base and verifies the denominator from native summary.h5, rather than assuming the multiplier from the input label.

    Run: https://github.com/kvnloo/openmc/actions/runs/36096538575

    Probe: https://github.com/kvnloo/openmc/blob/9f529781a81e6381174265b8330c111c67a66100/diagnostics/native_density_closure.py

    Would keeping the first regression focused on scatter and nu-scatter be useful before extending coverage to the fission scores?

    AI-assisted; actual CPU execution with unchanged production source. No fission or full-suite validation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions