Skip to content

fix: correct several neutron sampling / tally bugs#470

Merged
ilhamv merged 2 commits into
mcdc-project:devfrom
steps-re:fix/neutron-physics-sampling
Jul 15, 2026
Merged

fix: correct several neutron sampling / tally bugs#470
ilhamv merged 2 commits into
mcdc-project:devfrom
steps-re:fix/neutron-physics-sampling

Conversation

@steps-re

Copy link
Copy Markdown
Contributor

Five independent correctness fixes found while auditing the neutron physics and tally code. Each is explained below with the reasoning.

1. Delayed neutrons are never sampled (transport/physics/neutron/native.py, fission())

xi = rng.lcg(particle_container_new)   # xi in [0, 1)
total = nu_p                            # prompt MULTIPLICITY (~2-3)
if xi > total:                         # never true
    prompt = False
    ...

xi is a uniform [0,1) sample but total is set to the prompt multiplicity nu_p (order 2-3), so xi > total is always false and the delayed-neutron branch is dead. The subsequent loop accumulates delayed fractions into total, confirming total is meant to be the prompt fraction. Fix: total = nu_p / nu.

2. Delayed emission time uses β instead of λ (same file)

decay_rate = mcdc_get.nuclide.neutron_fission_delayed_fractions(delayed_group, ...)
...
particle_new["t"] -= math.log(xi) / decay_rate

Sampling an exponential emission time needs the delayed-group decay constant λ, but this reads the delayed fraction β. A dedicated neutron_fission_delayed_decay_rates getter already exists; use it.

3. Swapped transverse-basis branches (transport/distribution.py, sample_direction())

The branch that builds 1/sqrt(wx^2+wy^2) ran when abs(wz) >= 0.999 (axis ~parallel to z, where wx^2+wy^2 -> 0, dividing by ~zero), while the fixed basis (1,0,0),(0,1,0) ran otherwise (where it is not orthonormal to a general axis). The bodies were swapped and the # Axis nearly parallel to z comment was on the wrong branch. Swapped so the fixed basis handles the pole and the general construction handles everything else.

4. Divide-by-zero for a -z reference (transport/distribution.py, sample_white_direction())

if nz != 1.0: lets nz == -1.0 (an ordinary -z normal) into the branch that computes B = sqrt(1 - nz**2) = 0, then C = Ac / B. Guard with abs(nz) != 1.0; the else branch (using ny) already handles both poles.

5. Tally polar_reference corrupted (object_/tally.py)

self.polar_reference /= polar_reference / np.linalg.norm(polar_reference)

self.polar_reference defaults to [0,0,1]; dividing it element-wise by the normalized input yields [0, nan, inf] for e.g. [1,0,0]. Every sibling filter assigns directly — assign the normalized vector.

Testing

These are localized logic fixes; I verified each against the surrounding code and the referenced getters/constants but did not run the full numba/MPI suite locally. Happy to adjust to your conventions.

- fission(): prompt-vs-delayed test compared a U[0,1) sample against nu_p (the
  prompt multiplicity, ~2-3) rather than the prompt *fraction*, so `xi > total`
  was never true and delayed neutrons were never sampled. Use nu_p / nu.
- fission(): delayed emission time used neutron_fission_delayed_fractions (beta)
  where the decay constant is required; a neutron_fission_delayed_decay_rates
  getter already exists. Use it.
- sample_direction(): the transverse-basis branches were swapped -- the general
  1/sqrt(wx^2+wy^2) construction ran when the axis was ~parallel to z (dividing
  by ~0), and the fixed basis ran otherwise (not orthonormal to a general axis).
  Swap them (and fix the misplaced comment).
- sample_white_direction(): `if nz != 1.0` let nz == -1 through, giving B = 0 and
  a divide-by-zero for a -z reference normal. Guard with abs(nz) != 1.0; the else
  branch already handles both poles.
- Tally: polar_reference used `/=` against the normalized input, turning the
  default [0,0,1] into [0, nan, inf]; assign the normalized vector instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@steps-re
steps-re changed the base branch from main to dev July 11, 2026 00:06
@ilhamv

ilhamv commented Jul 11, 2026

Copy link
Copy Markdown
Member

Hi, @steps-re. Thanks for bringing these up and fixing them!

Some of the errors were introduced during the major refactoring, and the others were just not properly tested.

After a parametric testing, it is found that two regression tests already cover some of the errors (failing right now since they use wrong test answers):

  • moving_source detects Error 3 (swapped transverse-basis branches in source sampling_direction)
  • basic_weight_window detects Errors 1 and 2 (continuous-energy delayed neutron fission sampling; the use of time_boundary covers the incorrect delayed emission)
    (I went ahead and updated the failing regression test answers.)

Errors 4 and 5 are currently not covered by the tests. We can add a unit or regression test to cover those two. Do you want to add it as part of this PR, @step-re? Or, we can make a follow-up Issue for the need for the new test.

This is an important update. Once merged, we will make the patch release v0.14.2.

Thanks, @steps-re!

  • Update the CHANGELOG.md

@ilhamv
ilhamv self-requested a review July 11, 2026 03:32
@ilhamv

ilhamv commented Jul 15, 2026

Copy link
Copy Markdown
Member

The need for tests to cover the fixed Errors 4 and 5 will be brought up again after #472 is resolved and we proceed with completing test coverage.

@ilhamv
ilhamv merged commit cf77e9b into mcdc-project:dev Jul 15, 2026
12 checks passed
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