Skip to content

feat(rad,observer): two-way state-dependent attenuation, poison burnout, and 4-panel observer contract - #81

Draft
aurascoper wants to merge 5 commits into
masterfrom
feat/two-way-attenuation-observer
Draft

aurascoper wants to merge 5 commits into
masterfrom
feat/two-way-attenuation-observer

Conversation

@aurascoper

@aurascoper aurascoper commented Nov 8, 2026 •

Copy link
Copy Markdown
Owner

Summary

This draft PR proposes the rebased two-way attenuation and 4-panel observer branch (feat/two-way-attenuation-observer) for independent scientific and software review rather than direct merge into master. It also documents the single-line physical-row requirement on data/deferrals.csv in scripts/preflight_merge.sh.

Scientific & Software Review Scope

  1. Conservation and normalization of the two-group attenuation cascade (biofilms_potts.jl)

    • Directional ray-marching in update_radiation_field! integrates optical_depth_fast += kappa_eff * ds with I_f_step = I0 * exp(-optical_depth_fast) and I_therm_acc = I_therm_acc * exp(-kappa_th_eff * ds) + Sigma_eff * I_f_step * ds.
    • Effective moderation cross-section is bounded by local fast removal (0 <= Sigma_eff = clamp(Sigma_loc, 0.0, max(0.0, kappa_loc)) <= kappa_eff) with kappa_th_eff = max(0.0, p.kappa * p.kappa_therm) >= 0, ensuring 0 <= I_fast <= I0, I_therm >= 0, radiation == I_fast + I_therm, and 0 <= radiation <= I0 (never multiplying above the unattenuated source even if nominal Sigma_scatter > kappa_species).
    • Verified in BiofilmsPottsRad.run_control_audit() and test_viewer_bundle.py.
  2. Energy and state accounting in the poison-burnout surrogate (biofilms_potts.jl)

    • Explicit 5-point stencil in step_fields! with reflection (zero-flux Neumann) boundary conditions satisfies sum(lap) == 0.0 to machine precision on arbitrary grids.
    • Before non-negativity clamping, total grid inventory obeys the exact discrete balance sum(C_new) - sum(C_old) == dt * sum(prod - decay - burn).
    • Verified in BiofilmsPottsRad.run_control_audit() and test_viewer_bundle.py.
  3. Distinguishing native OpenMC fields from resampled/extruded display fields (viewer.py, observer.py, biofilms_potts.jl)

    • Every LayerSpec carries native_openmc_field=False, grid_representation="resampled_extruded_display_grid", and authoritative_for_quantitation=False.
    • BiofilmOpenMCObserver declares NATIVE_OPENMC_FIELD = False and GRID_REPRESENTATION = "resampled_extruded_display_grid", and export_observer_payload stamps "native_openmc_field": false.
  4. Read-only observer isolation (observer.py, viewer.py)

    • BiofilmOpenMCObserver sets READ_ONLY_OBSERVER = True and AUTHORITATIVE_FOR_QUANTITATION = False, never imports feedback_uq or synthetic_gate, and has no write/feedback/verdict methods.
    • compute_and_validate_layers copies all input numpy arrays so downstream rendering cannot mutate caller raw_state buffers in place.
  5. Confirming synthetic neutron/dose outputs cannot be mistaken for measured physical dose (viewer.py, observer.py, biofilms_potts.jl)

    • LayerSpec sets synthetic_surrogate=True and measured_physical_dose=False, with flux units "rel_flux" (never "Gy" or "Sv").
    • Panel 3 scalar bar is explicitly titled "Flux I_tot [rel, synthetic surrogate - NOT Gy]".
    • Replaced legacy radiotrophic_fraction key with melanized_fraction (MELANIZED_FRAC_KEY).
  6. Deferral register CSV single-line scope (scripts/preflight_merge.sh)

    • Documented that data/deferrals.csv is validated line-by-line and requires single-line rationales (RFC 4180 single-line subset; embedded newlines inside quoted fields are not supported).

…, bilinear poison burnout, and 4-panel observer contract
Copilot AI balanced review requested due to automatic review settings November 8, 2026 06:00

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

A critical radiation-energy error charges an unchanged source site, allowing identical final lattices to incur different costs, while other unresolved findings affect transport physics, reported quantities, and production-kernel test coverage.

11 open issues
What changed in this PR

This draft adds a synthetic, state-dependent radiation and poison-burnout model alongside the existing biofilm simulation, with a four-panel observer for inspecting its outputs.

Changes:

  • Adds a Julia two-group attenuation model, coupled lattice updates, inhibitor dynamics, control audits, and JSON export.
  • Adds Python layer contracts, synthetic-field labels, observer rendering, telemetry, and tests.
  • Makes the snapshot module’s h5py import optional and documents single-line deferral CSV requirements.
File Description
scripts/​preflight_merge.sh Documents the existing single-physical-line CSV restriction; no behavior change.
coupling/​tests/​test_viewer_bundle.py Adds layer validation, observer rendering, telemetry, and contract checks.
coupling/​biofilm_openmc/​viewer.py Defines surrogate layer contracts and computes derived display fields.
coupling/​biofilm_openmc/​snapshot.py Allows module import when h5py is unavailable.
coupling/​biofilm_openmc/​observer.py Adds the four-panel observer and summary telemetry.
biofilms_potts.jl Adds the radiation/burnout surrogate, control audits, and observer payload export.

Review effort: Balanced

Comment thread biofilms_potts.jl
Comment on lines +2507 to +2508
ΔI = state.radiation[i_tgt, j_tgt] - state.radiation[i_src, j_src]
return (β_src - β_tgt) * ΔI
Comment thread biofilms_potts.jl Resolved
Comment thread coupling/biofilm_openmc/observer.py Outdated
poison = np.asarray(layers["xenon_inhibitor_C"], dtype=np.float64)
burn = np.asarray(layers["bilinear_burnout_sink"], dtype=np.float64)
return {
"mean_dose_rate": float(np.mean(rad)),
Comment thread coupling/biofilm_openmc/observer.py Resolved
Comment on lines +712 to +725
categories=True,
cmap="tab10",
clim=(1, 5),
annotations=SPECIES_ANNOTATIONS,
show_edges=True,
scalar_bar_args={"title": "Species ID (Thresholded > 0)"},
)
else:
pl.add_mesh(
grid,
scalars="species_id",
categories=True,
cmap="tab10",
clim=[-1, 5],
Comment thread coupling/biofilm_openmc/viewer.py Resolved
Comment thread coupling/biofilm_openmc/viewer.py Resolved
Comment on lines +731 to +734
baseline_flux = float(params.get("I0", 1.0)) * float(
np.exp(-0.5 * kappa_water)
)
void_reactivity = (mean_flux - baseline_flux) / max(mean_void, 1e-6)
Comment on lines +575 to +579
out = compute_and_validate_layers(raw, params)
assert np.all(out["fast_flux"] >= 0.0) and np.all(out["fast_flux"] <= params["I0"])
assert np.all(out["thermal_flux"] >= 0.0)
assert np.allclose(out["total_radiation"], out["fast_flux"] + out["thermal_flux"])
assert np.all(out["total_radiation"] <= params["I0"] + 1e-12)
Comment thread coupling/tests/test_viewer_bundle.py Resolved
Copilot AI balanced review requested due to automatic review settings November 8, 2026 06:24

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The copy-attempt energy violates detailed balance and the two-group update loses flux under pure scattering, while existing controls do not exercise the production kernels.

12 open issues
2 previously missed issues

In code that hasn't changed since last review

Medium severity Sum melanized species only when their types exist

biofilms_potts.jl:2725

N_types is configurable, but this reduction unconditionally indexes species 4 and 5. A valid single-species or three-species state initializes successfully and then throws a BoundsError when computing or exporting its derived layers. Sum only melanized species that exist in sp_counts.

Medium severity Compare against a matched no-void flux reference

biofilms_potts.jl:2739

This compares a domain-mean two-group flux with a fast-only water value at one assumed path length, so the difference is not attributable to voids. An all-water 8×8 lattice with the default cascade has no voids but reports a coefficient of approximately 139,274 because the unrelated residual is divided by 1e-6. Use a matched no-void calculation and explicitly handle zero void fraction, or label this as a flux residual rather than dI/df_void; the same issue occurs in coupling/biofilm_openmc/viewer.py:732–735.

Review effort: Balanced

Comment thread biofilms_potts.jl Resolved
@aurascoper
aurascoper force-pushed the feat/two-way-attenuation-observer branch from 3ef965e to 8ac0e45 Compare November 8, 2026 06:35
Copilot AI balanced review requested due to automatic review settings November 8, 2026 06:35

Copilot AI left a comment

Copy link
Copy Markdown

Comment thread biofilms_potts.jl Resolved
Comment thread biofilms_potts.jl
5. Bilinear burnout hysteresis and discrete zero-flux Neumann inventory balance (`sum(lap) == 0.0` and exact step accounting).
6. Exact volume registry conservation (`cell_vol[cid] == count(==(cid), σ)` for all cells).
"""
function run_control_audit()
…manuscript and enforce embedded-newline CSV refusal
Copilot AI balanced review requested due to automatic review settings November 8, 2026 07:03
@aurascoper

Copy link
Copy Markdown
Owner Author

Addressed follow-up items in 69da568:

  1. Enforced single-line CSV rationale rule with negative control: scripts/preflight_merge.sh explicitly rejects any physical line in data/deferrals.csv with an odd number of double quotes (unclosed quotes or multi-line quoted fields split across \n) as MALFORMED DEFERRAL ROW, and scripts/test_preflight_merge.sh adds Control (8d) asserting that a quoted rationale containing an embedded newline exits 1 with MALFORMED DEFERRAL ROW.
  2. Removed duplicate branch ref: Deleted fix/issues-solo-boost on origin and locally so only feat/two-way-attenuation-observer points to PR feat(rad,observer): two-way state-dependent attenuation, poison burnout, and 4-panel observer contract #81.
  3. Manuscript two-way surrogate prose update: Updated preprint/modeling_radioresistance_and_radiotropic_fitness.tex (Section 5 Methods line 600, Section 6 Results line 923, and Appendix planned-feedback line 1716) to state that while the executed OpenMC Monte Carlo snapshot exchange in the 256-seed ensemble is one-way, radiation coupling in the repository code is no longer strictly one-way when the opt-in state-dependent attenuation, two-group cascade, and poison-burnout surrogate (BiofilmsPottsRad, target_calibration=false) is active.

Copilot AI left a comment

Copy link
Copy Markdown

Copilot AI balanced review requested due to automatic review settings November 8, 2026 07:28
…n vs one-way burnout, integrate exact two-group transfer, and wire Julia controls
@aurascoper
aurascoper force-pushed the feat/two-way-attenuation-observer branch from b907a5d to 677297e Compare November 8, 2026 07:31

Copilot AI left a comment

Copy link
Copy Markdown

Comment thread biofilms_potts.jl
elseif u < void_fraction + 0.22
continue
end
sp = clamp( 1 + floor(Int, ((i + j) % p.N_types)), 1, p.N_types )
Copilot AI balanced review requested due to automatic review settings November 8, 2026 07:37

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread biofilms_potts.jl
Comment on lines +2743 to +2744
baseline_flux = p.I0 * exp(-0.5 * p.κ * p.κ_water)
void_reactivity = (mean_flux - baseline_flux) / max(mean_void, 1e-6)
scalar_bar_args={"title": "Inhibitor C [a.u.]"},
)
if float(np.max(active_layers["bilinear_burnout_sink"])) > 1e-6:
point_grid = grid.cell_data_to_point_data()
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