Implementation-preservation audit

The frozen scientific implementation remains under radial_letter_v0_1_release/; RadialPaths does not edit or call its publication renderers. This audit records the exact behavior preserved by the package.

Geometry provenance

Canonical source: radial_letter_v0_1_release/analysis/v06_survey_native_benchmark/scripts/prepare_v06_benchmark.py.

Quantity

Frozen implementation

Source

Support-constrained distance

MCP_Geometric(np.where(support, 1.0, np.inf), fully_connected=True).find_costs(...); values outside support become NaN

distance_from_sources, lines 44–48

Per-centre distances

One graph-distance solve per supplied integer (row, column) centre

coordinate_fields, lines 51–54

Assignment a(x)

np.argmin over finite per-centre distances; exact ties go to the first supplied centre

lines 55–57

Regions B_k

support & (basin == k)

lines 66–67

Chosen-centre distance d_k

Gather the distance corresponding to the assigned centre

line 58

Boundary

In-support pixels adjacent in the 3×3 neighborhood to any excluded pixel

line 59

Boundary distance b

Same support-constrained graph solver with all boundary pixels as sources

lines 60–61

rho_D

dcore / (dcore + dboundary) where the denominator is positive

lines 62–63

L_k

Maximum assigned-centre distance over B_k

lines 65–69

rho_X

dcore / L_k within B_k

line 70

Observational coordinate storage

rho_D and rho_X are written as float32 before figure/profile use

lines 133–134

fully_connected=True means an 8-neighbour pixel graph. Unit in-support costs give axial steps cost 1 and diagonal steps cost sqrt(2) through MCP_Geometric. Internal excluded holes are therefore barriers to traversal and also contribute boundary-source pixels through the same 3×3 adjacency.

RadialPaths preserves these operations in src/radialpaths/geometry.py. It adds input validation for a non-empty, connected, two-dimensional support and for supplied centres inside that support. Those checks reject inputs outside the paper’s assumptions; they do not alter valid-case numerical results. The frozen implementation computes distances and extents in float64. Its observational preparation then serializes the normalized coordinate fields as float32 before the R profile estimator reads them. That storage step is needed for exact reproduction of bin membership at coordinate values lying on bin edges; it is not part of the mathematical definition.

Public precision and paper reproduction

The continuous mathematical definitions do not require float32 normalized coordinates. The public build_geometry therefore returns float64 distances, extents, rho_D, and rho_X. Exact distance ties remain assigned to the first supplied centre, as in the validated implementation. Reordering the centre list can consequently reassign tie pixels and can alter region-normalized rho_X values when the reassignment changes a region extent. rho_D and the minimum centre-distance field are unchanged by a pure centre permutation.

The observational preparation for Figures 4–5 serialized rho_D and rho_X as float32 FITS arrays. That implementation detail is retained only by radialpaths.reproduction.build_paper_geometry. The frozen regression tests call that explicit compatibility path. They do not set the public numerical default.

Profile provenance

Canonical source: radial_letter_v0_1_release/mnras_submission/scripts/make_jades_figure.R, function binned_profile, lines 189–204. The synchronized renderer at analysis/figure_visual_system/render_jades_figures.R:156–173 is equivalent.

For each centre-associated region independently:

  1. use 30 equal coordinate bins on [0, 1];

  2. select rho >= lower and rho < upper;

  3. include rho == 1 in the last bin;

  4. exclude non-finite tracer and coordinate pixels;

  5. omit bins with fewer than six pixels;

  6. report the unweighted median and the 16th and 84th percentiles.

RadialPaths preserves this estimator as the default in src/radialpaths/profiles.py. It does not reproduce the later figure-only normalization by image peak or display floor, because those are plotting operations rather than the profile estimator.

Regression fixture

tests/data/reference_geometry_profile.npz is generated by tools/generate_regression_fixture.py directly from the frozen coordinate_fields function and an explicit Python transcription of the published R binning loop. The fixture contains a connected, non-convex, two-centre support with an internal hole, a deterministic tracer, and all expected geometry/profile arrays. tests/data/regression_manifest.json records generator/source hashes and library versions.

The regression test requires exact basin/boundary agreement and floating-point agreement at rtol=1e-13, atol=1e-13 for finite geometry and profile values.

End-to-end observational cross-check

The packaged implementation was also run directly on the two frozen Figure 4–5 supports and supplied centres. Against the stored FITS products for both GZMERGER09 and GZMERGER29:

  • centre-assignment arrays agree exactly;

  • float32 rho_D arrays agree exactly, including NaN locations;

  • float32 rho_X arrays agree exactly, including NaN locations;

  • all 30-bin per-centre pixel-count arrays agree exactly;

  • all populated unweighted-median values agree exactly (maximum absolute difference 0.0).

The explicit float32 coordinate finalization is part of that end-to-end match. Without it, the transient float64 coordinate fields differ from the stored products by only about 3e-8, but a small number of rho_D values cross exact bin edges. Preserving the serialization behavior prevents that downstream change.

The frozen preprocessing fixture also stores the candidate peak values used during the paper audit. Current SciPy releases reproduce their ordering, positions, masks, selected centres, geometry, and profiles, but can differ in the stored peak values by a few units in the last floating-point place. The unchanged exact-value test is therefore run in the recorded validation environment in requirements/paper-reproduction.txt. This lock applies only to paper reproduction; the public API retains its broader declared dependency ranges.