Kinder & Faulkner · manuscript draft, not submitted · branch research/observable-split-and-pilot-hardening
A mechanistic modelling framework for a seven-species biofilm community under ionising radiation, plus the calibration and transport machinery that decides what the framework is allowed to claim.
Four things run. A 3D Cellular Potts model (biofilms_potts.jl) evolves biomass parcels on a
cylindrical lattice under a four-term Metropolis acceptance functional — adhesion, volume, radiation
and melanin — with a static radial radiation field and a genuinely two-way melanin field. A
cylindrical radiodialysis transport model (biofilms_radiodialysis.R, and a second
implementation inside the CPM) solves a three-equation contaminant/sorbate/membrane system in which
membrane permeability is a dose-driven field rather than a fixed material constant — the most
interesting mechanism in the repository. An OpenMC photon-transport path (coupling/) builds
transport models from a lattice snapshot, tallies heating, converts it to absorbed dose and
attributes that dose to lineage labels. A calibration layer (calibration/) gates all three, and
currently refuses to emit a single physical unit.
julia --project=. biofilms_potts.jl # coupled CPM + radiodialysis, N = 40, 100 MCS, seed 42(That command has one open defect — see Dependencies.)
The framework is uncalibrated. Its parameters are literature priors and declared model inputs. No lattice pitch, no seconds-per-Monte-Carlo-step and no material density has been selected, and no output has been compared to a measurement of a target system. Every simulation number below is reported in the dimensionless model units it was computed in.
On the manuscript's status: two web searches on 2026-08-15 found no record of a bioRxiv posting, and no DOI appears anywhere in the repository. Its own final section is titled "Notation Fixes Required Before Submission". Treat it as unposted — that is a search result, not proof.
Five phenomena travel under one loose word and they are not interchangeable. A survival curve cannot
calibrate a directional term. The full argument is in docs/calibration/integration_contract.md.
| Phenotype | What it means | Represented here? |
|---|---|---|
| radiotropic | directional redistribution toward or away from a source | yes — this is response.hamiltonian |
| radiotrophic | radiation measurably enhances growth or metabolism | no — requires growth, and there is none |
| radioresistant | elevated survival after exposure | no — requires death, and there is none |
| melanized_radioprotective | melanin measurably alters damage, survival or shielding | no — requires damage |
| radiation_responsive | molecular, morphological or material change after exposure | partly — response.melanin |
compute_delta_H adds β_ion[s]·I_γ when a parcel gains a site, so a negative β_ion lowers
the acceptance functional in high-dose sites and the parcel drifts up the gradient. That is a
tropism. It was previously labelled "metabolic energy gain", which conflates a Metropolis
acceptance functional in arbitrary units with an energy budget; the model creates no biomass for such
a gain to act on.
Radiotrophy and radioresistance are therefore structurally absent here, for the same reason
response.growth_survival is unsupported_by_current_model. They are not weakly supported. A model
with no birth and no death cannot express either, and no quantity of data changes that without a
model revision.
This has a consequence worth stating plainly: the published D10 evidence — which is strong, and is
what anchors the per-species β_ion magnitudes — is radioresistance evidence being spent on a
tropism coefficient. That substitution is legitimate as a declared modelling choice, and it is now
declared rather than left implicit in a sign convention. The constant is RADIOTROPIC in
biofilms_potts.jl:49, renamed from RADIOTROPHIC; biofilms_3d.R's species flag likewise.
This is the most interesting single result in the repository, and it is a self-audit finding rather
than a simulation output. At the shipped constants I0 = 1.0 and T_cpm = 5.0:
| Term | ΔH | Metropolis acceptance bias |
|---|---|---|
ΔH_rad, radiotropic species (β = −5e−5) |
−5.0e−5 | 1.000010 |
ΔH_rad, most radiosensitive species (β = 7.5e−2) |
+7.5e−2 | 0.985 |
ΔH_mel at the reported M = 1.44 |
−0.720 | 1.155 |
β_ion — the one parameter Table 2 tabulates per species, and the one the entire sign convention is
written around — biases acceptance by one part in 10⁵ for exactly the species whose radial
stratification is the headline result. The melanin term biases it by 15.5%: four orders of
magnitude more, through a coefficient of 0.5 hard-coded at its call site in compute_delta_H,
appearing in no table and in no configuration file.
Radiation still reaches the dynamics. It reaches them indirectly, because melanin_drive is
copied from the radiation field:
radiation → production (α_M, tabulated) → M → ΔH_mel (0.5, untabulated) → tropism
Two consequences. A sensitivity analysis over β_ion would report the radiation response as
negligible — right about the coefficient, wrong about the mechanism. And only the product
α_M · k reaches the dynamics, so cpm.melanin_coupling and response.melanin are jointly
identifiable at best and never separately. The coefficient is now ledgered as cpm.melanin_coupling
in data/parameter_provenance.csv with status=blocked and sensitivity_rank=high. It is one of
five rows in that ledger not ranked unknown, and the only one outside the A0 transport sweep — the
other four (transport.mesh.base_dimension, transport.mesh.coarsening_factor, transport.batches,
transport.particles) were ranked by that sweep and carry sensitivity_domain = transport_numerical, which is a numerical-convergence rank and not a biological one. Every
biological-response row in the ledger is still unknown, because no sensitivity analysis has been
run over any of them; this one was measured by hand.
For scale: adhesion differences in the hand-specified J matrix are O(4–20) per neighbour pair, so
ΔH_adh remains the largest term and still sets the sorting structure. The ordering is
adhesion ≫ melanin ≫ radiation.
Each refusal below is enforced in code and pinned by a test. None of them is a caveat added in prose.
| Not claimable | Why | What would clear it | Where enforced |
|---|---|---|---|
| Any length in µm or cm | No lattice pitch is selected. A pitch is not identifiable from a cell volume alone: V_physical = V_sites · a³ constrains only a product. select_pitch() refuses without declared tolerances, refuses when any tolerance is unset, and refuses when no evaluation is marked held-out. An undeclared tolerance is never read as infinitely permissive. |
A calibrated 3-D stack of a target system with a genuine held-out acquisition, plus populated tolerances in config/cpm_spatial_acceptance_template.toml. |
docs/calibration/cpm_spatial_calibration.md, calibration/biofilm_calibration/spatial/pitch_selection.py |
| Any duration in seconds, or any dose in Gy | No seconds/MCS conversion exists. CPMParams.seconds_per_mcs ships NaN and accrue_dose! raises rather than run without it. The repository has two independent clocks — MCS count and radiodialysis seconds — with no declared conversion. |
The dynamic observable is already selected (biomass_autocorrelation_decay, status provisional). dt_MCS = a² · S_sim / S_exp still needs a pitch, so this is blocked on the other half. |
docs/calibration/time_observable_contract.md, data/calibration/spatial/time_observable.csv, biofilms_potts.jl |
Any mass, concentration, or g cm⁻³ |
No wet bulk density and no closed elemental composition. data/calibration/voxel_binding.csv carries both numeric fields blank; blank means NOT MEASURED and is never read as zero. wet_bulk_density() raises when a wet mass is divided by a cells_only volume. |
Replicate wet and dry mass with matched hydrated and dry blanks, a hydrated volume on a whole_biofilm_envelope basis, ash, and CHNS + ICP closing to 1 on a wet-bulk basis — on one specimen under one condition. |
docs/calibration/material_basis_contract.md, calibration/biofilm_calibration/materials/ |
| Growth, division, survival, or extinction | response.growth_survival is unfittable in principle, not merely unmeasured: the CPM has no growth dynamics and divide_cell! has no trigger. |
A model change. No quantity of data clears it. | docs/calibration/integration_contract.md, calibration/biofilm_calibration/schema.py |
| Anything per cell | One CPM cell ID is a computational biomass parcel, with literal_generation_claim_allowed = false for all seven species. Reading a parcel-volume distribution as a cell-size distribution, or comparing parcel counts to colony-forming units, is refused. |
A model in which one ID is one organism — again a model change, not data. | docs/calibration/cpm_entity_semantics.md, data/calibration/spatial/entity_semantics.csv |
| A per-organism spatial prediction for B. subtilis, C. sphaerospermum or A. niger | Three of the seven species have morphologies the model cannot hold at any pitch — rods and filaments need surface-area, elongation and connectivity terms compute_delta_H does not have. Verdict: MODEL_REVISION_REQUIRED. |
Those terms. | docs/calibration/cpm_spatial_calibration.md, calibration/biofilm_calibration/spatial/representability.py |
| A physical apparatus | Nothing here defines a physical system. The legacy R visualisation uses normalised R = 1, L = 2, so no value in it becomes centimetres by assigning a unit. The CPM additionally forces L = 2R — a topology fact, not a resolution one — so domain.semantics = "microvolume" is the only honest declaration available. |
A declared apparatus with an independent length scale, which would also relax L = 2R. |
docs/physical_reference_system.md |
| A coupled dose-driven remediation loop | STOP CONDITION RETAINED: no OpenMC dose enters the radiodialysis model until the constitutive choice is explicitly selected. In the current code m and P_eff are independent open-loop functions of time; neither reads the other. |
An explicit constitutive selection for how damage maps to permeability, then import_dose_field! on a real path. |
docs/online_coupling_design.md, docs/audit_biofilms_potts.md §6, §10 |
Two rules the whole repository runs on:
- Empty is not zero. A blank numeric cell means "not measured" and reads back as
None. Nothing here substitutes0.0for a missing measurement — the single most dangerous default in a calibration ledger. blockedis notunsupported_by_current_model. A blocked quantity awaits a measurement; an unsupported one awaits a model change, and no quantity of data will clear it.
The repository root also carries unsorted media and data artifacts — PDFs, MP4s, Tableau workbooks,
TSV exports, loose GIFs and PNGs — that are not part of the framework and are not listed here. One
of them is no longer unsorted: geolocator/ and the power-plant dataset beside it are a maintained
subsystem with its own provenance and tests, described under
Primary Fuel Source Geolocator. It is still
outside the framework, and it is documented so that sharing a repository is not mistaken for coupling.
.gitmodules declares an unrelated submodule (Lumped-Uncertainty-SLS-MPC) that is not checked out
and is referenced by nothing; git clone --recurse-submodules pulls an MPC repository you did not
ask for.
Biofilms/
├── biofilms_potts.jl # 3D Cellular Potts Model + radiodialysis coupling (authoritative)
├── biofilms_potts_jacc.jl # JACC/AMDGPU port (8-colour checkerboard)
├── biofilms_radiodialysis.R # Radiodialysis membrane PDE — Shiny app
├── biofilms_3d.R # Cylindrical bioreactor — Shiny app (legacy)
├── biofilms.R # Flat-domain particle Langevin SDE + k-means (legacy)
├── reactor_decision_tree.R # k-NN / logistic sketch over 4 fuel candidates (illustrative)
├── validate_serial.jl # Serial-vs-JACC statistical comparison
├── export_checkpoint.jl # HDF5 checkpoint / transport-snapshot export
├── scoby_3d.jl # empty (0 bytes)
├── Project.toml, Manifest.toml, LocalPreferences.toml
│
├── calibration/ # biofilm-calibration (Python, numpy-only)
│ ├── biofilm_calibration/
│ │ ├── spatial/ # entity semantics, scale candidates, biomass-field
│ │ │ # coarse-graining, structure, occupancy, pitch selection,
│ │ │ # morphology, representability, time observable,
│ │ │ # ingest/readers, dataset screening
│ │ ├── materials/ # basis-tagged Quantity arithmetic, mixture/mass closure,
│ │ │ # seeded Monte-Carlo uncertainty, export gate
│ │ ├── integration.py # the voxel binding where the two branches join
│ │ ├── acquisition.py # acquisition contract, biosafety gate
│ │ ├── precision.py # derived replicate counts, detectability
│ │ └── schema.py # shared status / evidence vocabulary
│ ├── scripts/ # vcholerae_pilot (surrogate), detectability_pilot,
│ │ # emit_synthetic_reference_config, reference_d_status
│ └── tests/ # 14 modules, 237 tests collected
│
├── contract/ # biofilm-contract (Python; NO dependencies)
│ └── physical_contract/ # vocabulary, MaterialSpec, composition closure,
│ # source placement, git provenance, feedback
│ # gate verdicts. calibration/ may not import
│ # coupling/, so what both need lives here.
│
├── coupling/ # biofilm-openmc (Python; numpy, h5py)
│ ├── biofilm_openmc/ # config, model, materials, mesh, dose, lineage,
│ │ # snapshot, results, fingerprint, convergence, drivers,
│ │ # feedback_uq, feedback_gate
│ ├── scripts/ # a0_sweep, synthetic_e2e, import_dose_field.jl
│ ├── requirements.txt
│ └── tests/ # 14 unit modules + tests/integration/ (5, OpenMC-gated);
│ # 122 collected (6 skip without OpenMC)
│
├── config/
│ ├── coupling_template.toml # 21 REQUIRED-but-unset keys
│ ├── cpm_spatial_acceptance_template.toml # every threshold unset
│ ├── biofilm_material_acceptance_template.toml # every threshold unset
│ ├── reference_a0_water_phantom.toml # the first populated configuration
│ ├── reference_synthetic_biofilm_e2e.toml # synthetic; calibrates nothing
│ ├── reference_d_spatial_acceptance.toml # FROZEN declarations
│ ├── reference_d_material_acceptance.toml # FROZEN declarations
│ └── feedback_effect_policy_template.toml # threshold UNSET on purpose
│
├── data/
│ ├── parameter_provenance.csv # 44 rows × 21 columns, per-parameter provenance ledger
│ ├── sources.csv # 12 sources pinned by version / accessed_date / sha256
│ ├── a0_transport_convergence.csv # 100 data rows: the whole A0 sweep, 5×4×5
│ ├── calibration/
│ │ ├── spatial/ # entity semantics, structure, morphology, time observable,
│ │ │ # dataset_candidates.csv (+ .sha256),
│ │ │ # pilot_vcholerae.csv / .metadata.json / .preflight.json
│ │ ├── materials/ # blanks, bulk, components, elemental, metal loading (header-only)
│ │ ├── voxel_binding.csv # the join; both numeric fields blank
│ │ └── baseline_condition.csv # ships UNSET
│ └── research/ # radiotrophic compatibility audit ledgers
│ ├── radiation_response_candidates.csv # 64 data rows (116 preamble lines)
│ ├── radiotrophic_dataset_candidates.csv # 64 data rows
│ └── radiotrophic_materialization_candidates.csv # 26 data rows
│
├── docs/
│ ├── audit_biofilms_potts.md # the contract the coupling work was built against
│ ├── physical_reference_system.md, openmc_stack.md, exchange_schema.md
│ ├── online_coupling_design.md, jacc_coupling_port.md, adder_applicability.md
│ ├── a0_transport_resolution_decision.md, branch_report.md
│ ├── calibration/ # 8 contracts: entity semantics, spatial, time observable,
│ │ # material basis, material calibration, biomass field,
│ │ # integration, acquisition
│ └── research/ # radiotrophic compatibility audit, calibration map,
│ # lab gap, pilot recommendations
│
├── tests/ # runtests.jl, contract_csv.jl, deterministic_radiation.jl,
│ # genealogy_tests.jl, checkpoint_io_tests.jl, fixtures/
├── preprint/ # .tex (source of record), .md (revised derivative),
│ # figures/ (4 × PDF + PNG). The built .pdf was REMOVED:
│ # it was a pre-revision build carrying withdrawn claims
├── assets/ # preview_bioreactor_3d.png, preview_radiodialysis.png
├── kmeans_species_trajectory (1).gif, biofilm_dynamics_7_species.gif # embedded below
├── Citations.md # predates data/sources.csv, unmaintained
└── .github/workflows/ # coupling-tests.yml (4 jobs)
The equations in this section are the paper's target formalism. They are stated because they define the model being aimed at; what each program actually integrates is stated in Simulations and differs.
The fitness of each species
No program in this repository integrates this equation. No file defines a field biofilms.R and biofilms_3d.R integrate particle positions, not a field; the CPM is a Metropolis
Markov chain. Three of the nine terms are dimensionally checkable as written; the remainder contain a
symbol with no declared unit (
The multi-species Hamiltonian:
density_g_cm3 in data/calibration/voxel_binding.csv, currently blank and blocked on the material
gate. There is no velocity or momentum state anywhere in the repository, so the kinetic term has no
counterpart in any implementation, and the equation closes dimensionally only if every term is
re-declared as an energy density. This is an open defect, carried forward from the April draft's own
notation list.
Mutualistic pairwise interaction potential:
In the CPM this is a reporting statistic, not a force: total_pairwise_energy is called once per
snapshot and γ_mutual / σ_mutual appear nowhere in compute_delta_H. The two R models each use a
different ad-hoc attraction law.
Radiation field (Beer–Lambert, cylindrical source), as implemented in the CPM:
tests/deterministic_radiation.jl. No attenuation length in cm exists. biofilms_3d.R uses
Melanin reaction–diffusion:
Implemented in form. In the code
The most carefully derived component in the repository, implemented twice —
biofilms_radiodialysis.R (method of lines + deSolve::lsoda) and biofilms_potts.jl (method of
lines + forward Euler with an automatic stability substep) — with the two implementations in
agreement. It postdates the April manuscript and appears in no version of it.
Mobile contaminant (cylindrical reaction–diffusion):
Immobile phase (biosorption + bioreduction):
Membrane damage and dose-driven permeability:
Robin boundary condition at the membrane wall
Permeability as an evolving field driven by cumulative dose, rather than a fixed material property,
is the interesting mechanism here and it survives intact. What does not survive is the units in the
Julia coupling, where every physically-labelled slot receives a non-physical value: params.N/2 — 20 lattice units — fed into a cm² s⁻¹ operator; g cm⁻³ slots and multiplied by rates in cm³ g⁻¹ s⁻¹;
mean() before it
reaches the solver, so the uptake sink is spatially uniform.
The material gate records this as RADIODIALYSIS: BLOCKED — "blocked by a units error in the
model, not by missing data." The gate blocks the biomass basis fed into the Julia coupling, not
the PDE system or its discretisation, both of which are sound and cross-validated between the two
implementations.
Also:
Julia, stdlib only for the simulation core. Cylindrical lattice, Metropolis Monte Carlo, and a four-term effective energy in the acceptance path:
The pairwise mutualistic energy is computed once per snapshot as a diagnostic and does not influence
the dynamics. H_CPM is a Metropolis acceptance functional in arbitrary units, dissipated toward a
minimum — not a conserved thermodynamic energy, and there is no symplectic integration anywhere in
this repository.
Field coupling is partial: melanin is genuinely two-way (occupancy drives init_radiation! and nothing feeds back into
it; the nutrient field is written and never read by the dynamics.
julia --project=. biofilms_potts.jl # coupled CPM + radiodialysis
julia --project=. biofilms_potts.jl --no-radiolysis # CPM onlyRun provenance, both branches: the coupled default is N = 40, 6 parcels per species, 100 MCS,
seed 42. --no-radiolysis runs main(), which is N = 60, 8 parcels per species, 200 MCS,
seed 42. All reported numbers below come from the coupled N = 40 run.
Two stale stdout banners are an open defect. biofilms_potts.jl:1088 and :1529 both print
H = H_adh + H_vol + H_rad + H_pair + H_mel — five terms, where the acceptance path has four and
H_pair is a diagnostic. :1541 prints Ḋ(R)=%.1f Gy/s for a quantity that is a dimensionless
placeholder and for which seconds_per_mcs is NaN. Nothing enforces agreement between the banners
and the code, which is why they drifted; a reader who trusts the terminal is told two things this
README refuses.
Fig 1 — radial mean position by species over 100 MCS (N = 40, 6 parcels/species, seed 42). The
plotted observable is the mean parcel distance from the cylinder axis, in lattice units normalised by
R, sampled every 20 MCS. The radiation field is maximal on the axis, so the high-radiation region is
the core, not the wall; β_ion is negative for the two radiotropic fungi and positive for
B. subtilis.
Spatial sorting is dominated by the hand-specified adhesion matrix J. Within the radiation-derived
pathway, melanin dominates β_ion by four orders of magnitude (see the melanin finding above).
tests/deterministic_radiation.jl demonstrates the inertness directly: it must deliberately amplify
Fig 2 — mean melanin field value over occupied sites, per producing species, to MCS 100.
C. sphaerospermum reaches 1.44. The ordering among the three producers is set by the input α_M
(0.14 versus 0.10 and 0.065), and the linear rise is structural — the field equation has no
saturation, decay or substrate-limitation term. What is not set anywhere in the parameters is the
spatial distribution and magnitude of
biofilms_potts_jacc.jl is a JACC/AMDGPU port using 8-colour checkerboard updates. Its contract is
statistical, not pathwise, and docs/jacc_coupling_port.md records that it is never the first
causal comparison vehicle. The literal comment # 13. Figure export in biofilms_potts.jl is a
load-bearing split marker: tests/runtests.jl:14, validate_serial.jl:12 and
biofilms_potts_jacc.jl:454 all load the serial monolith by splitting the source on that exact
string and evaluating the first half in a sandbox module whose imports are hard-coded to
LinearAlgebra, Statistics, Random, Printf. The two rules are: never edit that line, and add nothing
above it that needs a non-stdlib package.
Shiny app. Method-of-lines finite-volume solver for the three-equation system, LSODA adaptive stiff
integration (deSolve), Robin condition at the wall and L'Hôpital treatment at
Rscript biofilms_radiodialysis.R # serves on 127.0.0.1:${SHINY_PORT:-7799}, no browser launchRscript is not present on the machine this README was verified against, so both Rscript commands
here are untested as written. The R app's units are declared but unvalidated. The Julia coupling's
units are wrong by construction (above).
Fig 3 — membrane integrity state.radiation, dt_rd = 0.5 carries the source
comment "tune to match CPM time scale", and seconds_per_mcs is NaN, so no conversion to grays
exists. The permeability ratio is exactly
Fig 4 — radial contaminant profile
Stale figure and label notice. All four committed figures predate the 2026-08-14 correction that
neutralised the reversed biological zone labels and removed the hard-coded dose annotation, so they
differ from current export_figures output. In addition, the embedded Fig 4 prints
(1.0 − c_mean) * 100 under the label "% depleted" — the figure says depleted where the model
supports only non-penetrated. The preview image above assets/preview_bioreactor_3d.png likewise
predates the 2026-08-14 radiation sign fix.
Both are declared legacy and non-authoritative. The Julia seven-species registry is authoritative.
biofilms_3d.R — cylindrical bioreactor, Shiny app. Overdamped dynamics in a cylinder of radius
Rscript biofilms_3d.R # auto-prints the shinyApp object, which starts the server (untested here)This file carries Pseudoalteromonas, which appears nowhere else in the repository, and omits
O. intermedium. Its species ordering differs from both other registries, so any positional
cross-file mapping is silently mismatched. Two open defects: heat and nutrient contributions are
scalars added identically to the
biofilms.R — flat-domain particle Langevin SDE. Seven entities (one an unnamed placeholder;
O. intermedium absent) on a dimensionless gamma_intensity is a scalar constant added identically to both velocity components, producing a
uniform drift along the diagonal. Results are qualitative only and support no gradient or
niche-partitioning claim.
Neither subsystem appeared in the April documents. Together they are now the largest part of the repository and they govern what the rest may claim.
An installable Python package, deliberately numpy-only, which must not import the coupling package. Two branches meeting at one contract:
- spatial — entity semantics, scale-candidate enumeration, biomass-field coarse-graining, structural observables, occupancy mapping, pitch selection, morphology and representability diagnostics, synthetic validation fields, the dynamic-observable contract, format-agnostic ingest with optional microscopy readers, and a public-dataset screening ledger.
- materials — basis-tagged
Quantityarithmetic, mixture and mass-closure checks, seeded Monte-Carlo uncertainty, and an export gate. - integration — the voxel binding that joins them, and the only place where a whole class of
error becomes visible.
docs/calibration/integration_contract.mdnames six rejected combinations whose individual clauses each pass their own branch's gate — the canonical one being a literal cell fragment, whose density comes from bulk wet biofilm, whose lineage is claimed as a biological cell lineage. Neither branch can see the contradiction alone. Only the join does. - campaign — the acquisition contract, derived replicate counts and detectability.
It selects no pitch, exports no material and emits no config, because no measured dataset of a target system exists.
The gates refuse, and the refusal is the tested behaviour. emit_transport_config() prints
REFUSING to emit a biofilm transport config with the full reason list. apply_occupancy() refuses
while the mapping is unset, and mass_preserving refuses without a declared seed — an unseeded
lattice cannot be reproduced or reviewed. precision.detectability() refuses and names the right
remedy: more biomass per coupon, or a substrate with a lighter, more reproducible blank — not more
replicates. The material export gate blocks proxy, derived_proxy, synthetic, declared and
unresolved evidence, because shipping it would launder it into a measurement.
The dataset ledger records rejections as first-class results.
data/calibration/spatial/dataset_candidates.csv holds 12 screened candidates: 1 use_first,
3 inspect_first, 2 diagnostic_only, 6 rejected. The informative half is what was turned down —
a search that only records what it accepted is not a provenance trail. Two none_found rows are
recorded as findings: no volumetric dataset covers the seven-species community, and no public record
pairs a calibrated 3-D volume with wet mass, dry mass and matched blanks for one system. That is a
search result, not proof of non-existence — but it is enough to conclude that the target campaign
should be designed rather than waited for. The most instructive rejection is an excellent
exact-species S. oneidensis single-cell tracking dataset, rejected on representational grounds
because it answers the wrong question — independent confirmation that single_cell_msd was correctly
rejected as a seconds/MCS observable.
Biosafety follows strains, not species. Several components are commonly BSL-1 while
C. neoformans strains are commonly BSL-2, and collection material under one species name can carry
different assigned levels, so a species list cannot determine a facility class. The enforced chain is
exact strain IDs → institutional review → containment decision → approved procedures, and the gate
blocks on a missing institutional_approval_id. Nothing in this repository describes a
field-deployable system.
calibration/scripts/vcholerae_pilot.py ran against Dryad doi:10.5061/dryad.zcrjdfnph (CC0), with
reference_system_id = public_vcholerae_surrogate and target_calibration = false written into its
own output so the distinction survives being copied out of context. V. cholerae is not one of the
seven modelled species. A successful run licenses exactly one claim — that the spatial calibration
machinery was exercised against a named public 3-D biofilm reference system — and never that a
biological pitch was calibrated.
Preflight found no file in the Dryad set that is both 3-D and time-resolved. The spatial pass
therefore ran on Dataset_3_Fig3c_VPS-Bap1.nd2, a static Z = 94 × 3-channel × 1024² stack with
voxel size (0.065, 0.065, 0.13) µm read from the file rather than typed, has_time_axis: false,
time_metadata_source: "absent". The temporal observable stayed blocked — not deferred, blocked,
because the file has no time axis to observe.
Two rows in data/calibration/spatial/pilot_vcholerae.csv:
| coarsening | pitch µm | passed | biovolume frac | porosity | interface area | corr. length | thickness | comp. size q50 |
|---|---|---|---|---|---|---|---|---|
| 1 | 0.065 | true | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 2 | 0.130 | false | 0.0 | 0.0 | 0.463 | 0.260 | 0.235 | 15.0 |
Row 1 passes by construction — it is the field compared against itself, so every error is identically zero and it is not evidence of anything. Row 2 is the result: one factor-of-two coarsening destroys every structural observable, worst of all the median connected-component volume at a relative error of 15.0, while the two intensive volume-fraction observables — biovolume fraction 0.047702 and porosity 0.952298 — hold exactly. Volume fraction is preserved by block averaging; geometry is not. That is the loss curve, and it is steep.
Both rows carry segmentation_basis = unresolved (the channels are fluorescent reporters, and a
microscope records what a channel detected, not what it is intended to represent) and
holdout_kind = none, since no frame of one file is an independent held-out stack — so select_pitch
refuses these rows regardless of the numbers.
The dataset download was declined rather than defeated on the first attempt: the host's
proof-of-work is a deliberate control against exactly the kind of client this is. Four SHA-256
digests are recorded so a manual copy verifies byte-for-byte, and the input digest is recorded as
sha256_verified: true in the run metadata.
A defect was found and fixed by this pilot. nd2.ND2File.voxel_size() returns (1.0, 1.0, 1.0)
for a file that carries no calibration, and the reader's old v > 0 guard passed that straight
through as a calibrated 1 µm voxel. The guard now consults axesCalibrated
(calibration/biofilm_calibration/spatial/readers.py). This is the pilot's most durable output.
An installable Python package (deps exactly numpy, h5py) that builds OpenMC photon-transport
models from a CPM lattice snapshot, tallies heating, converts it to absorbed dose, attributes dose to
lineage labels, and evaluates a two-way-feedback gate offline. One console script,
biofilm-dose-scan. The online (two-way) driver is NOT IMPLEMENTED; its design and its gates are
recorded instead.
Real transport has run against a pinned stack: conda-forge openmc 0.15.3, Python 3.13, HDF5 1.14.6,
official ENDF/B-VIII.0 photoatomic data with recorded SHA-256.
The OpenMC result is a feasibility benchmark, not a physical calibration.
config/reference_a0_water_phantom.toml says it of itself: "This is a numerical-validation geometry,
NOT an apparatus. Every value below is either evaluated nuclear data or a DECLARED modeling choice —
none of it is measured, and none of it describes a real reactor." All dose values are voxel-averaged
absorbed dose under OpenMC's charged-particle local-deposition approximation, not single-cell
microdosimetry.
What it demonstrated:
- Energy balance closes to a relative error of 5.2 × 10⁻⁹ (1 000 000.0 eV deposited per 1 MeV source photon in a reflective water box), and uncollided transmission through 1, 2 and 3 cm of water is exponential within 15%.
- The feasibility run reported its own bad news: 75% of occupied voxels scored no heating at the 10 µm demo tally pitch, median relative error ≈ 1. Recorded, not hidden. The 10 µm feasibility mesh is a tally pitch and never a biological pitch.
- The A0 sweep (100 runs + 1 reference: 5 coarsening factors × 4 history counts × 5 seeds, plus a
48³ / 6.4 × 10⁷-history reference at an unswept seed) sets the A0 mesh and history count and
establishes a hard ceiling of
coarsening_factor ≤ 4. Sparsity is a scale problem, not a method problem, and the A0 geometry does not have it. Two metrics are reported because one of them cannot see discretization:field_diff_vs_referencehas zero discretization error by construction on nested meshes,resolution_lossdoes not — conflating them would have made every mesh look convergent.
Every physical value ships REQUIRED-but-unset in config/coupling_template.toml (14 keys, staged
transport → dosimetry → cpm_feedback → membrane_feedback), and the loaders enumerate every missing
key in one error and refuse. biofilm-dose-scan exits 2 with the complete missing-key list rather
than evaluating.
Block sum versus block mean, in two packages, pinned by tests in both. Deposited energy is
extensive, so coupling/.../mesh.py:coarsen_field block-sums; a biomass volume fraction is
intensive, so calibration/.../spatial/field.py:coarse_grain block-averages. Same concept,
opposite operation, and using the wrong one is off by factor³. The two packages deliberately do not
import each other, so the distinction is restated where it can be seen. (The V. cholerae pilot is
the same fact from the other side: block averaging preserved volume fraction exactly and destroyed
every geometric observable.)
The array boundary is not an interface, and an edge-truncated chord is not a chord. The first
version of specific_interface_area did count the boundary, and a test caught it: the metric changed
when the same slab was cropped.
docs/research/radiotrophic_compatibility_audit.md (2026-08-15) is an evidence audit across eleven
data repositories and the sequence archives, run as seven parallel retrieval tracks each re-verified
by a second pass, with the ledgers in data/research/. It populates no parameter. Nine access
controls were encountered and none was circumvented.
| Question | Verdict |
|---|---|
| Exact radiotrophic data for the modelled species | TARGETED_LAB_EXPERIMENT_REQUIRED |
| CPM calibration from public data | PUBLIC_DATA_CAN_VALIDATE_PIPELINE_ONLY |
| Materialization (density, composition) | TARGET_WET_BULK_MEASUREMENT_REQUIRED |
| Integrated (one system, all components) | PUBLIC_COMPONENTS_FOUND_BUT_NOT_PAIRED |
The headline: radiotrophy is not established for any of the seven species. The claim rests on one 2007 paper whose exposure was characterised by analogy rather than by a stated dosimeter, whose growth effects are single-digit percentages, and whose integrated cell dose is not stated. Against it stand a same-species independent report of no substantial melanin protection under gamma, a same-species negative melanin-pathway result at 1 and 3 kGy, a same-genus null under a TLD-anchored ¹³⁷Cs field, an isogenic null in A. niger across three ionizing modalities, and a formal energy-budget critique. Peer review struck the word from the flagship ISS title. No author contact resolves this, because the discriminating experiment has not been run by anyone.
Radioresistance, by contrast, is well evidenced — and it is the evidence that anchors the
per-species β_ion magnitudes, which is exactly why the phenotype table above matters.
Radiotropism has its own candidate and it is blocked twice. Zhdanova 2004 reports directed hyphal growth toward a source; whether its dose gradient was measured or calculated is unverified, and directed hyphal elongation has no CPM representation, because a parcel is isotropic and there is no elongation or connectivity term. One blocker awaits a measurement, the other awaits a model change.
Public exact-species volumetric imaging exists for exactly one of the seven (B. subtilis), and for none of the four with radiation data. Two of the seven have no ionizing-radiation record in the audit at all. O. intermedium AM7 has a total public footprint of 1086 base pairs and one paywalled abstract, and has never been irradiated in any public experiment.
From the coupled CPM + radiodialysis run in biofilms_potts.jl: N = 40, 6 parcels per species,
100 MCS, seed 42, single realisation, no replicates. Not from the preprint — none of these numbers
appears in it. Every value below is dimensionless; nothing in this table is in grays, seconds,
centimetres or grams, because no conversion to any of those exists.
| Result | Value | Status |
|---|---|---|
| C. sphaerospermum melanin, MCS 100 | 1.44 |
Dimensionless field units. Mean of a dimensionless field over occupied sites. The ordering among the three producers is set by the input α_M; the magnitude and spatial distribution are emergent. |
| Melanin acceptance bias at that value | 1.155 (15.5%) |
Dimensionless. Computed from the shipped 0.5 coefficient and T_cpm = 5.0; four orders of magnitude above the β_ion bias of 1.000010. The dominant radiation-derived term, and undeclared until 2026-08-15. |
| Membrane integrity after 100 MCS | m = 0.779 |
Dimensionless. No physical dose exists: Ḋ = 1.0 is a placeholder, dt_rd = 0.5 is an uncalibrated MCS→second knob, seconds_per_mcs is NaN, and accrue_dose! raises rather than run without it. There is no value of Gy this corresponds to. |
| Permeability ratio | P_eff / P₀ = e ≈ 2.72 |
Dimensionless, and exact by construction. A closed-form function of four hand-set constants (P₀ rescaled by that factor, and a literature prior is not a calibration. |
| Interior contaminant, unweighted node mean | c / c_ext = 0.024 |
Dimensionless ratio. Measures non-penetration, not depletion: c(t=0) ≡ 0 everywhere, so the interior never held contaminant to remove. The mean is unweighted over radial nodes, not volume-weighted. The embedded figure labels this "% depleted"; the figure is wrong and the model supports only non-penetration. |
| Radial position of parcels | C. neoformans mean r/R = 0.65; B. subtilis mean r/R = 0.50 |
Dimensionless, and not yet evidence. Both terms are lattice units, so the ratio is legitimate. But the figures come from main_coupled() at N = 40, six parcels, 100 MCS, so R = 20 and parcel centres are rejection-sampled uniformly over radial_dist < R − 4 = 16 = 0.8R, giving a centres-only null of E[r/R] = ⅔(0.8) ≈ 0.53 — the B. subtilis value sits 0.43 SE from it, indistinguishable from the initial condition; n = 6 parcels on one seed gives SE ≈ 0.077 against a 0.15 gap, ≈ 1.4 SE of the difference; and the direction is opposite to the implemented physics, since a negative β_ion biases C. neoformans toward the high-radiation axis. Note that 0.58 is itself only an approximation: parcels occupy volume and grow toward V_target, so the mean over occupied sites is not the mean over seed centres, and the true null has to be simulated rather than derived. No multi-seed run against a uniform-seeding control exists in the repository. |
| Pairwise community energy | −34.4 → −41.5 |
Model units, diagnostic only. total_pairwise_energy never enters Metropolis acceptance, so nothing in the simulation minimises it. It records mutualistic-pair proximity under adhesion, not a selected-for outcome. |
| Parcel persistence | 42 / 42 seeded biomass parcels, where 42 = 7 species × 6 parcels is the ceiling as well as the floor |
A property of the model, not a survival result. There is no division (divide_cell! has no trigger) and the only death path is volume erosion under a volume constraint that opposes it. Parcels are not cells. response.growth_survival is unsupported_by_current_model. |
On the "self-regulating remediation loop." The intended design is: radiation damages the membrane
→ P_eff rises → more contaminant enters → metal-reducing S. oneidensis, co-located by CPM
dynamics, immobilises it. Every arrow in that chain is currently open. m and P_eff are
independent laws and neither reads the other; P_eff is a function of import_dose_field! exists to make a real link explicit, and no run
currently invokes it. Wiring the loop is gated on the retained m-versus-P_eff stop condition. It
is a design hypothesis; nothing in this repository tests it.
The vocabulary is the code's.
spatial: READY_FOR_TIME_CALIBRATION | PROVISIONAL | MODEL_REVISION_REQUIRED | NOT_EVALUATED
material: READY_FOR_OPENMC_BIOFILM_TRANSPORT | READY_FOR_RADIODIALYSIS_MAPPING
| PROVISIONAL | BLOCKED | NOT_EVALUATED
feedback: PASSED | FAILED | NOT EVALUATED
row status: ready | provisional | blocked | unresolved | not_applicable
| unsupported_by_current_model
| Gate | Verdict |
|---|---|
| CPM spatial | PROVISIONAL — entity semantics are declared for all seven species; everything else is missing |
| Material, OpenMC path | OPENMC: PROVISIONAL |
| Material, radiodialysis path | RADIODIALYSIS: BLOCKED — blocked by a units error in the model, not by missing data |
| Voxel binding | coherent, incomplete · Emission: REFUSED — a coherent sentence with blanks in it is still a sentence with blanks |
| Offline feedback gate | NOT EVALUATED — by design |
| Online (two-way) coupling | NOT IMPLEMENTED |
| JACC coupling port | design only — no JACC coupling code exists on this branch, deliberately |
| Time observable | SELECTED: biomass_autocorrelation_decay, row status = provisional (runner-up interface_rearrangement_rate); seconds/MCS still blocked on the pitch half |
| Baseline condition | Ships UNSET — a protocol contract, not a claim about a specimen that exists |
| Reference D declarations | FROZEN — all twelve modelling decisions made, reference_d_acceptance_v1; three declared criteria carry enforcement = not_implemented and may not close a gate |
| Reference D campaign | CAMPAIGN_READY: no — blocked by exactly one thing, D-APPROVAL, which only a biosafety committee can issue |
| Offline feedback gate | NOT_EVALUATED — no effect threshold declared, and none may be defaulted |
| Online feedback | DISABLED, fail-closed — every prerequisite defaults to its refusing value |
Provenance ledger distribution (data/parameter_provenance.csv, 49 rows × 21 columns): status —
22 ready, 10 not_applicable, 9 blocked, 7 provisional, 1 unsupported_by_current_model.
model_compatibility — 34 direct, 10 requires_transform, 5 unsupported. A row whose status is
ready must carry a source_id; unresolved and blocked rows must not carry a value.
julia --project=. tests/runtests.jl # 135 passed, 0 failed (re-run 2026-08-15 at HEAD)
julia --project=. biofilms_potts_jacc.jl --selftest
pip install -e "coupling[dev]" && (cd coupling && pytest -rs tests) # 86 collected
pip install -e "calibration[dev]" && (cd calibration && pytest -rs tests) # 173 collectedThe Julia figure is 2 + 34 + 57 + 42 across the four suites, re-run on this tree rather than quoted
from docs/branch_report.md, which records the older 2026-08-13 branch-end figures and is stale on
the Python counts.
Run together in the coupling venv, the two Python suites give 259 passed, 4 skipped
(re-run 2026-08-15 at HEAD). All four skips are coupling modules that skip on import openmc in a
bare venv — the three in coupling/tests/integration/ plus coupling/tests/test_model_build.py.
No calibration test skips: the pilot ND2 file is present on this machine, so test_pilot.py runs.
The OpenMC integration tier is manual opt-in: activate the openmc-biofilms
environment, set OPENMC_CROSS_SECTIONS, then run the coupling suite. CI is
.github/workflows/coupling-tests.yml — julia-tests, python-unit, calibration-unit, and
openmc-integration behind a workflow_dispatch input.
What the tests pin:
tests/contract_csv.jl— byte-identical CSV output against a golden fixture, pinning the serial RNG stream. The fixture is Julia-1.12-dependent.tests/deterministic_radiation.jl— field shape (maximum on the axis, monotone radial decay), the exact local ΔH sign per species, and multi-seed drift direction with a deliberately amplified β, because the production β is too small to be detectable. The local sign is a contract; the collective drift is a result.tests/genealogy_tests.jl— the lifecycle/dose contract, and that the windowed API is identical to the legacy loop.tests/checkpoint_io_tests.jl— snapshot orientation probes and bit-exact restart resume.coupling/tests/test_mesh.py— the nesting invariant and the coarsening trap: taking the dose denominator fromvoxel_pitch_cmafter coarsening understates every dose by exactlyfactor³.coupling/tests/test_fingerprint.py— thattransport_state_hashexcludessource.photons_per_secondwhiledose_state_hashincludes it.calibration/tests/test_spatial_schema.py—test_gate_is_provisional_not_ready,test_gate_returns_model_revision_required_for_a_literal_cell_claim,test_gate_is_not_evaluated_without_semantics.calibration/tests/test_material_basis.py— thatsite_occupancyis refused everywhere a concentration is required.calibration/tests/test_integration_binding.py— the six incoherent bindings.calibration/tests/test_precision.py— that extra imaging fields buy nothing when between-batch variance dominates.
preprint/modeling_radioresistance_and_radiotropic_fitness.tex is the source of record:
Modeling Radioresistance and Radiotropic Fitness in Multispecies Biofilms — A Computational
Framework, Implementation Audit, and One-Way Radiation-Transport Foundation.
It reports executed computational work and an implementation audit. It infers no target physical parameter, and its transport section reports that the one-way CPM–OpenMC path executed rather than reporting results from it — no gate verdict, no lever magnitude, no dose in physical units. The Ethics statement records that the work involved no culture, no recombinant or synthetic nucleic-acid experiment and no irradiation, so no institutional approval was required for it, and that any future campaign waits on prospective strain-, protocol-, facility- and source-specific approvals.
It cites the parent of the commit that adds it. A document cannot cite the commit that
contains it, which is the same structural limit the run artifacts record for their own provenance
— git_commit there names the parent and carries a -dirty marker for the same reason.
The repository has moved past that revision, and some of what it moved past was our own
published numbers. A false-positive control that could not fail, a squared difference reported
as a variance, an unprovenanced sensitivity table, and one synthetic-gate verdict withdrawn as not
resolution-converged. None of those numbers appears in the manuscript. The full record is
data/claims_ledger.csv and docs/calibration/.
The superseded sources are in git history, not in the tree. modeling_radiotrophic_fitness.{md,tex}
and the pre-revision PDF carried a fabricated Computational Methods section, a Th-232 comparison
against data that exists in no data/ file, organisms named in no file here, and a symplectic
integrator the Project.toml has never contained. Those claims are catalogued as 115 PP-* rows
in the claims ledger, 34 of them delete.
And the ledger is now enforced. calibration/tests/test_claims_ledger.py asserts that no
claim marked delete reappears in the manuscript. Until it existed, nothing in the repository
checked the ledger at all.
Its real coverage is 20 of the 34, and the other fourteen are named in the test output as still needing a human. An earlier version of this note said "30 of 34", which counted rows that yield a searchable phrase rather than rows the guard can actually find — many ledger entries are reconstructions across the two superseded sources rather than verbatim quotes from either. The guard is calibrated against the pre-revision manuscript recovered from git history: it must detect at least 18 of the deleted claims there, because a guard that cannot find known-bad input is not a guard, and the first version of it detected 12.
DOI archiving is an author action: preprint-v1 is the tag to archive, and enabling Zenodo
requires the account owner.
Figures are generated by biofilms_potts.jl below its split marker. The .tex includes them
without an extension, so a pdflatex build resolves preprint/figures/*.pdf.
This supports no claim in this repository. The geolocator is a visualization and infrastructure-data subsystem that shares this repository. It is not coupled to the Cellular Potts model, the radiodialysis transport stack, the calibration harness, or the claims gates. No gate reads it, no figure depends on it, and nothing it renders is evidence for anything the framework asserts.
It is documented for the opposite reason to an unexplained artifact. A stray file invites a reader to infer a relationship that does not exist; a named, maintained subtree invites the same inference unless the boundary is stated. This is the statement.
geolocator/ serves a globe of the world's power plants over the WRI Global Power Plant Database
(v1.3.0, CC BY 4.0), vendored here as an immutable snapshot rather than fetched at run time, so a
given commit always means a given dataset. power_plant_database_global.provenance.json records the
publisher, version, licence, canonical source and the SHA-256 of the committed bytes. A test checks
that hash against the file, because a provenance declaration that can drift from what it describes
turns unknown into confidently wrong. The dataset is pinned -text, since under line-ending
normalisation a content hash is not reproducible across platforms and the declaration would be
unverifiable by construction.
The part worth borrowing is the freshness model, which refuses to let one signal stand for another. Reload detection and freshness authority are separate: a stat fingerprint decides when to reread a file, while a timestamp inside the data decides whether it is current. Touching a file rereads it and changes nothing else, because touching a file does not make the world newer. Sources declare what kind of thing they are — a reference dataset has a declared vintage and is never stale by clock, since a versioned snapshot does not drift toward wrong; a live source is measured against a clock; an authored source carries no semantic timestamp at all and reports that absence rather than letting a file modification time impersonate one. Absence of an authority is a state to publish, not a gap to paper over.
The stack is FastAPI and three.js with no build step; run it with
python3 -m uvicorn geolocator.api:app. Its own tests live in geolocator/tests/. An optional local
market adapter is not included in this repository, and its absence is reported explicitly rather than
rendering as an empty result — an uninstalled producer must not read as a producer that found
nothing.
The Tableau workbook and screen recording at the repository root are this subsystem's ancestor, retained for provenance. They plot the same dataset coloured by primary fuel. They are not the implementation, and neither they nor it bear on the biofilm work.
Julia 1.12 — the tested version; the CSV golden fixture pins the RNG stream to it.
Project.toml [deps] is exactly AMDGPU, HDF5, JACC. HDF5 is required by
export_checkpoint.jl; JACC (with AMDGPU) by biofilms_potts_jacc.jl.
Open defect: CairoMakie is undeclared. biofilms_potts.jl:1874 does using CairoMakie at top
level, below the # 13. Figure export split marker, so
julia --project=. biofilms_potts.jl fails at that line unless CairoMakie happens to be present
in the default environment. That is a missing dependency declaration, not a design choice: the
split-marker rule constrains only what appears above the marker, and the sandbox module the
splitters build has hard-coded imports that never consult Project.toml. Until it is declared, run
the simulation from an environment that also has CairoMakie.
R — deSolve, shiny, plotly, ggplot2, dplyr, MASS, class (the last two for
reactor_decision_tree.R).
install.packages(c("deSolve", "shiny", "plotly", "ggplot2", "dplyr", "MASS", "class"))No R version floor is enforced or evidenced in the code, and no R command in this README has been executed on the machine it was verified against.
Python ≥ 3.11 (both pyproject.toml files) — two independent packages, neither importing the
other. The pinned OpenMC stack is Python 3.13.
pip install -e "calibration[dev]" # numpy only
pip install -e "coupling[dev]" # numpy, h5pyOpenMC is optional and out-of-band: a pinned conda-forge stack (openmc 0.15.3, HDF5 1.14.6,
ENDF/B-VIII.0 photoatomic data with recorded SHA-256), documented in docs/openmc_stack.md. Until it
is installed, every OpenMC-dependent result is reported as blocked or skipped, never as passed.
The manuscript is unpublished and has no DOI. Cite the repository at a commit.
Kinder, H., Faulkner, B. (2026). Modeling Radioresistance and Radiotropic Fitness:
Spatiotemporal Dynamics of Microbial Communities Under Ionizing Radiation Stress.
Unpublished manuscript.
https://github.com/aurascoper/Biofilms, commit 5d1b777.
data/sources.csv is the working source registry, pinning each cited document by
document_version, accessed_date and sha256, and it records honest failures alongside successes —
two rows read NOT LOCATED and NOT VERIFIED against the primary report. Citations.md predates
that registry and is not maintained.
See LICENSE.txt.






.gif)
