Summary
Under the settings the NISAR rubbersheet workflow passes to it (critical_value=0.1 and max_iterations=len(data); the function's own default budget is 50), isce3.math.offsets_polyfit.polyfit_offsets can turn a change of one correlation-grid step (1/32 px) in a single input offset into a wholesale change of the final inlier set and a fitted-surface jump three orders of magnitude above the continuous response. The module is byte-identical on develop and v0.25.16 (last touched by PR #173), so this is not a regression report. The question we would most like answered: is crit_value = 0.1 intended as a threshold on the componentwise standardized w-test statistic (classically of order unity to several), or was "0.1 px" the intent — and is the full rejection budget intentional? We sketch a few directions below and welcome comments; if useful, we can implement the ones you prefer and report comparative results.
Minimal reproduction (self-contained)
"""One-quantum (-1/32 px) change of ONE input offset discontinuously
changes the final inlier set of the isce3 rubbersheet polyfit
(pinned synthetic case; production fit settings)."""
import numpy as np
from isce3.math import offsets_polyfit as op
GRID = (41040, 52906) # full radar grid of the real pair
SENSOR = dict(prf=1520.0, abw=1263.68013808518,
rsr=48000000.0, rbw=40000000.0) # workflow-call values
Q = 1.0 / 32.0 # Ampcor correlation-grid quantum
CL = (0.4, 1.2, -0.6, 0.3, -0.2, 0.15) # degree-2 truth, azimuth
CP = (-0.2, 0.5, 0.8, -0.25, 0.1, -0.3) # degree-2 truth, range
# 30x30 sample grid; the driver sits at the node nearest the real
# driver's radar position and carries its real corr_peak 0.9485.
rng = np.random.default_rng(29) # pinned existence-proof seed
lines = np.linspace(0, GRID[0] - 1, 30).round()
pixels = np.linspace(0, GRID[1] - 1, 30).round()
driver = int(np.argmin(abs(lines - 23405))) * 30 \
+ int(np.argmin(abs(pixels - 42589)))
ll, pp = (a.ravel() for a in np.meshgrid(lines, pixels, indexing="ij"))
n = ll.size
A = op.build_design_matrix(ll, pp, 2, 0, GRID[0], 0, GRID[1])
tl, tp = A @ np.array(CL), A @ np.array(CP)
coh = np.zeros(n, bool) # small coherent elite in a junk
coh[rng.choice(n, size=int(round(0.08 * n)), replace=False)] = True
coh[driver] = True # majority, as in production
dl = np.where(coh, tl + rng.normal(0.0, 0.02, n),
tl + rng.uniform(-20.0, 20.0, n))
dp = np.where(coh, tp + rng.normal(0.0, 0.02, n),
tp + rng.uniform(-20.0, 20.0, n))
w = np.where(coh, rng.uniform(0.3, 0.8, n),
rng.uniform(0.02, 0.15, n))
dl[driver], dp[driver], w[driver] = tl[driver], tp[driver], 0.9485
dl = (np.round(dl / Q) * Q).astype(np.float32).astype(np.float64)
dp = (np.round(dp / Q) * Q).astype(np.float32).astype(np.float64)
w = w.astype(np.float32).astype(np.float64)
data = np.column_stack([np.arange(n, dtype=float), ll, pp, dl, dp, w])
def fit(d): # the production call shape
return op.polyfit_offsets(
d.copy(), degree=2, crit_value=0.1, max_iterations=len(d),
minL=0, maxL=GRID[0], minP=0, maxP=GRID[1], **SENSOR)
base = fit(data)
flipped = data.copy()
flipped[driver, 3] -= Q # THE perturbation: one azimuth
flip = fit(flipped) # offset of one sample, one quantum
inl_b = set(base["inliers"][:, 0].astype(int))
inl_f = set(flip["inliers"][:, 0].astype(int))
gl, gp = np.meshgrid(np.linspace(0, GRID[0], 101),
np.linspace(0, GRID[1], 101), indexing="ij")
jl, _ = op.predict_offsets(gl, gp, flip["coefL"] - base["coefL"],
flip["coefP"] - base["coefP"], 2,
0, GRID[0], 0, GRID[1])
it = (flip["removed_indices"].index(driver)
if driver in flip["removed_indices"] else None)
print(f"baseline: {len(base['removed_indices'])}/{n} removed, "
f"driver {'IN the final inliers' if driver in inl_b else 'OUT'}")
print(f"flip : driver removed at index {it} of "
f"{len(flip['removed_indices'])} removals (0-based), "
f"final inliers {len(inl_b)} vs {len(inl_f)}, "
f"common {len(inl_b & inl_f)}")
print(f"jump : induced azimuth-surface RMS "
f"{np.sqrt(np.mean(jl.ravel() ** 2)):.3e} px")
Output (isce3 0.26.0-dev at 2919e1c97, numpy 1.26.4, thread env pinned to 1):
baseline: 868/900 removed, driver IN the final inliers
flip : driver removed at index 832 of 870 removals (0-based), final inliers 32 vs 30, common 25
jump : induced azimuth-surface RMS 2.751e-02 px
The only difference between the two fits is flipped[driver, 3] -= Q: one azimuth offset of one high-weight sample, moved by exactly one Ampcor correlation-grid step. Everything else — all 900 samples, all weights, all fit settings — is bit-identical. The script needs an isce3 Python environment (NumPy, and SciPy — the module under test imports it transitively); nothing else. An extended version of this case with 8 pass/fail checks lives in our benchmark repository: repro_polyfit_quantum_membership.py (8/8 PASS in the two software environments we tested on the same host — numpy 1.26.4 and 2.2.6 — with an identical discrete removal chain; coefficient tails differ by up to ~2 ULP; other CPU architectures and BLAS implementations are untested). The pinned seed is an existence proof found by a documented 40-seed calibration hunt; no prevalence claim is made.
The synthetic case and the recorded real-data replay show the same discrete signature
| Check |
Synthetic-900 |
Recorded 40k replay |
| Baseline retention |
3.6% (868/900 removed), driver survives |
4.2%, driver survives |
| Flip -1/32 px at the driver |
removed at index 832 of 870 removals (0-based) = 95.6% of the chain |
removed at index 36,565 of 38,465 (0-based) = 95.1% |
| Final membership |
32 vs 30 inliers, 25 common |
1,677 vs 1,535, 985 common |
| Induced azimuth jump |
RMS 2.75e-2 px (max 0.106 px) |
RMS 3.60e-2 px |
| Tolerance bound vs half-quantum |
0.0132 px < 0.0156 px |
same (production kwargs) |
Mechanism: two-stage amplification
- Quantizer (subpixel argmax). The correlation-surface argmax maps input differences to "exactly zero or at least one grid step": whenever two implementations rank neighboring grid points differently — and cross-backend bit identity (e.g. CPU vs GPU Ampcor) is not generally guaranteed — the returned offset moves by at least q = 1/32 px. The quantizer is not the defect; the question is the robustness of the downstream removal chain to such small, valid input perturbations.
- Membership amplifier (sequential worst-outlier removal). Each iteration refits, then removes the sample with the largest combined squared standardized residual (
argmax(wL² + wP²)); the stop test is componentwise (max(|wL|, |wP|) <= crit_value). The fit response to an input offset is piecewise-smooth: linear while the removal chain is unchanged, with discontinuous jumps where a removal decision flips. Chains fork from perturbations as small as ~1e-4 px and almost always re-converge benignly; across a membership boundary, the jump is neither proportional nor monotone in the perturbation (3.6e-2 px from one quantum on the recorded data, vs the ~1e-5 px continuous-response scale).
A simple condition exposes the susceptibility:
crit_value * sigmaL / w = 0.1 * 0.1247 / 0.9485 = 0.0132 px
< q/2 = 1/64 = 0.0156 px
(half-bin bound on the nearest-grid quantization error)
The exact stop tolerance at a sample is crit_value * sigma * sqrt(1/w² - h_ii), bounded above by crit_value * sigma / w, so a sufficiently high-weight sample can fail the stop test on quantization error alone. This is a property of the high-weight tail — at the survivor-median weight 0.56 the bound is 0.0223 px and the inequality reverses — and it neither forces a deep purge nor guarantees a membership jump. On the recorded data the susceptibility was realized: 95.8% of the 40,000 samples were removed and the fit was decided by a ~4% high-weight elite (a crit_value sweep shows the flip response dropping 160-fold already at 0.2).
Questions and possible directions
The main question is the one in the Summary: the intended statistical interpretation of crit_value = 0.1, and the intent of the full rejection budget (max_iterations = len(data)). Beyond the synthetic case, we hit the mechanism in production form as a CPU-vs-GPU product-level difference (see "How we found it" below). The sketches below are for discussion, not a pre-made PR — we would rather agree on a direction first, and we are happy to implement the candidates you prefer and evaluate them comparatively on the synthetic reproducer and the recorded case.
Direction sketches (semantics calibration, observability, quantization-aware deadband, stable trimming, smooth losses)
- Confirm and calibrate the
crit_value semantics. The stop test compares componentwise standardized w-test statistics |e| / (s * sigma) against crit_value. Classical w-test critical values are typically of order unity to several, depending on the significance design (e.g. Baarda's B-method); for high-weight, low-leverage samples a threshold of 0.1 corresponds to demanding residuals at roughly one tenth of the prior noise level. If a re-calibration is considered appropriate, we can implement candidate calibrations and report their effect on both the reproducer and the recorded case.
- Observability and guardrails — report
n_removed / the inlier fraction next to the coefficients the workflow already prints, warn on extreme retention, optionally a max-rejection / min-inlier policy. Detection, not a cure; a min-inlier cap trades off stopping before the w-test criterion is met.
- Quantization-aware residual model / deadband. The offsets entering the fit are quantized at a configuration-known q (the Ampcor correlation-surface oversampling), so q is available to the fit. Numbers from the recorded case: adding q²/12 to the prior variance alone moves sigmaL from 0.124705 to 0.125031 px (+0.26%) and the driver tolerance bound from 0.013148 to 0.013182 px — still below q/2 = 0.015625 px, so it is nearly ineffective by itself. An explicit tolerance floor at q/2 (deadband on the stop test) is the variant that bites; the crit-0.2 sweep point (160-fold response drop) is adjacent empirical support.
- Batch / stable trimming instead of one-at-a-time hard deletion — reduces the sensitivity of the chain to a single flipped decision.
- Comparative evaluation of smooth robust losses (convex Huber-class IRLS): a unique convex solution avoids hard membership deletion and is expected to remove this jump class, but is a broad behavior change; Tukey-style non-convex losses can retain basin sensitivity, so no blanket guarantee is claimed — an evaluation candidate, not a promise.
Where the relevant pieces live
Code excerpts (pinned at 2919e1c97)
# python/packages/isce3/math/offsets_polyfit.py, L192-199
def polyfit_offsets(
data,degree=2,
crit_value=0.1,
max_iterations=50,
minL=None, maxL=None,
minP=None, maxP=None,
prf=None, abw=None,
rsr=None, rbw=None):
# python/packages/isce3/math/offsets_polyfit.py, L325-343 (trimmed)
# w-test
s = np.sqrt(diag_Qe)
wL, wP = (eL[:, 0] / (s * sigmaL)), (eP[:, 0] / (s * sigmaP))
max_any = max(np.abs(wL).max(), np.abs(wP).max())
if (max_any <= crit_value) or (iteration >= max_iterations):
return { ... }
# Remove worst outlier
worst = int(np.argmax(wL * wL + wP * wP))
removed_indices.append(int(data[worst, 0]))
data = np.delete(data, worst, axis=0)
A = np.delete(A, worst, axis=0)
# python/packages/nisar/workflows/rubbersheet.py, L169-177
results = offsets_polyfit.polyfit_offsets(
data,
degree=rubbersheet_params['polyfitting']['degree'],
crit_value=rubbersheet_params['polyfitting']['critical_value'],
max_iterations=len(data),
minL=minL, maxL=maxL,
minP=minP, maxP=maxP,
prf=prf, rbw=rbw,
abw=abw, rsr=rsr)
How we found it (recorded production comparison)
This is not hypothetical: we first hit the mechanism as a ~3.6e-2 px RMS difference between the CPU and the GPU InSAR workflow products on a NISAR L-SAR pair, and a controlled replay of the production fits attributed that difference to exactly one such quantum flip. In the extracted sample sets, ~99.97% of the 40,000 sample rows were bit-identical between the CPU and GPU runs, and every difference was at least one quantum. The full report, the recorded artifacts and the replay harness are public: report · artifacts · replay/probe harness.
The synthetic reproducer above is independently runnable by anyone. The real-data replay results are public recorded evidence: the source rasters and the extracted sample matrices are not published; we can archive the extracted matrices in a DOI-backed repository (e.g. Zenodo) after confirming redistribution and citation metadata.
Recorded 40k replay: transplanting one sample row reproduces the logged coefficient delta
Dataset context: ascending track 139, frame 019 (L1 RSLC), processed once with the CPU and once with the GPU InSAR workflow. The two runs' RIFG pixelOffsets layers differ by a smooth ~3.6e-2 px RMS degree-2 bowl; a linear regression on the offsets difference explains ~99.5% of the unwrapped phase-difference variance between the runs.
Sample-set counting: two independent CPU-Ampcor runs were each compared against the GPU-Ampcor baseline on the production sample grid (run A: 10 of 40,000 rows differ; run B: 9 rows; the union is 12 changed rows, 7 common to both).
All replay fits are CPU-only and thread-count controlled; A/A reruns are bit-identical.
| Result |
Value |
| Self-consistency gate |
the replayed fit reproduces the on-disk culled surfaces to 6.1e-15 px and the logged coefficients to print precision (4.1e-9) |
| Full-input swap (controlled CPU-Ampcor offset set into the GPU baseline) |
reproduces the observed CPU-minus-GPU coefficient target: cosine 1-6e-15; the replayed coefficients match the CPU run's logged values at max abs diff 5.0e-8 |
| Channel attribution |
offsets-only = full target; weights-only (38.5k float32 epsilons in corr_peak) forks at iteration 468 and re-converges benignly (2.4e-9 px) |
| Minimal destructive set |
ONE sample row (corr_peak 0.9485, azimuth offset differing by exactly -1/32 px, no range difference) — necessary (complement transplants change the coefficients by exactly zero) and sufficient (the single transplant = full target) within the observed difference sets; the other 11 rows of the 12-row union, including a 5.6/13.9 px (azimuth/range) outlier row, change nothing |
| Perturbation basin at the driver |
only the exact -1/32 px value lands in the target basin; -1e-5 px does not fork the chain; -1e-4 px and ±2-quantum deltas fork and re-converge benignly |
| Endgame membership |
final inliers 1,677 (GPU baseline) vs 1,535 (CPU-offsets), 985 common; the driver is removed at index 36,565 of the 38,465-removal sequence (0-based; 95.1% of the chain); the amplification curve stays at 1e-5..1e-3 px mid-run and explodes over the last ~2,000 iterations |
The endgame-membership figures are recorded in the membership summary JSON in the artifact directory; the remaining table entries map to the replay/probe JSONs there.
Scope and limitations
- Single dataset, single pair; replay determinism claims are same-environment claims. "Exact" means exact to the available production log precision.
- The real-data evidence is a controlled substitution on recorded rasters. The actual production CPU run's raw offsets were not reconstructed; "the one flip existed in the actual CPU run" is consistent with the coefficient-level evidence (the replayed coefficients land on the CPU run's logged values at 5.0e-8) but is not established.
Further limitations
- Pairwise interaction of the 12 changed rows was not enumerated (the complement probes close the necessity question for the observed sets).
- The synthetic two-population weight structure is a simplification of the production
corr_peak distribution, not a quantitative match.
- Why the CPU and GPU correlation surfaces rank neighboring grid points differently in that one window (the origin of the argmax flip) is out of scope here.
- The mechanism is an existence result under production kwargs; on this very dataset 11 of the 12 changed rows and all 38.5k weight epsilons were benign.
Appendix: docstring mismatch in the same module
The polyfit_offsets docstring describes minL/maxL (minP/maxP) as bounds on acceptable offsets ("Points with dL outside [minL, maxL] are removed before fitting") and removed_indices as including points removed "by hard bounds". The implementation uses these parameters only as coordinate-normalization bounds for the design matrix (defaulting to the data's line/pixel coordinate range); no pre-fit removal by offset bounds exists. Happy to fold a docstring fix into whatever comes out of the discussion, or file it separately.
Disclosure: The diagnosis and harness were written with assistance from AI agents. I ran the diagnostic scripts myself and reproduced the reported numbers before posting.
Summary
Under the settings the NISAR rubbersheet workflow passes to it (
critical_value=0.1andmax_iterations=len(data); the function's own default budget is 50),isce3.math.offsets_polyfit.polyfit_offsetscan turn a change of one correlation-grid step (1/32 px) in a single input offset into a wholesale change of the final inlier set and a fitted-surface jump three orders of magnitude above the continuous response. The module is byte-identical ondevelopandv0.25.16(last touched by PR #173), so this is not a regression report. The question we would most like answered: iscrit_value = 0.1intended as a threshold on the componentwise standardized w-test statistic (classically of order unity to several), or was "0.1 px" the intent — and is the full rejection budget intentional? We sketch a few directions below and welcome comments; if useful, we can implement the ones you prefer and report comparative results.Minimal reproduction (self-contained)
Output (isce3 0.26.0-dev at
2919e1c97, numpy 1.26.4, thread env pinned to 1):The only difference between the two fits is
flipped[driver, 3] -= Q: one azimuth offset of one high-weight sample, moved by exactly one Ampcor correlation-grid step. Everything else — all 900 samples, all weights, all fit settings — is bit-identical. The script needs an isce3 Python environment (NumPy, and SciPy — the module under test imports it transitively); nothing else. An extended version of this case with 8 pass/fail checks lives in our benchmark repository:repro_polyfit_quantum_membership.py(8/8 PASS in the two software environments we tested on the same host — numpy 1.26.4 and 2.2.6 — with an identical discrete removal chain; coefficient tails differ by up to ~2 ULP; other CPU architectures and BLAS implementations are untested). The pinned seed is an existence proof found by a documented 40-seed calibration hunt; no prevalence claim is made.The synthetic case and the recorded real-data replay show the same discrete signature
Mechanism: two-stage amplification
argmax(wL² + wP²)); the stop test is componentwise (max(|wL|, |wP|) <= crit_value). The fit response to an input offset is piecewise-smooth: linear while the removal chain is unchanged, with discontinuous jumps where a removal decision flips. Chains fork from perturbations as small as ~1e-4 px and almost always re-converge benignly; across a membership boundary, the jump is neither proportional nor monotone in the perturbation (3.6e-2 px from one quantum on the recorded data, vs the ~1e-5 px continuous-response scale).A simple condition exposes the susceptibility:
The exact stop tolerance at a sample is
crit_value * sigma * sqrt(1/w² - h_ii), bounded above bycrit_value * sigma / w, so a sufficiently high-weight sample can fail the stop test on quantization error alone. This is a property of the high-weight tail — at the survivor-median weight 0.56 the bound is 0.0223 px and the inequality reverses — and it neither forces a deep purge nor guarantees a membership jump. On the recorded data the susceptibility was realized: 95.8% of the 40,000 samples were removed and the fit was decided by a ~4% high-weight elite (acrit_valuesweep shows the flip response dropping 160-fold already at 0.2).Questions and possible directions
The main question is the one in the Summary: the intended statistical interpretation of
crit_value = 0.1, and the intent of the full rejection budget (max_iterations = len(data)). Beyond the synthetic case, we hit the mechanism in production form as a CPU-vs-GPU product-level difference (see "How we found it" below). The sketches below are for discussion, not a pre-made PR — we would rather agree on a direction first, and we are happy to implement the candidates you prefer and evaluate them comparatively on the synthetic reproducer and the recorded case.Direction sketches (semantics calibration, observability, quantization-aware deadband, stable trimming, smooth losses)
crit_valuesemantics. The stop test compares componentwise standardized w-test statistics|e| / (s * sigma)againstcrit_value. Classical w-test critical values are typically of order unity to several, depending on the significance design (e.g. Baarda's B-method); for high-weight, low-leverage samples a threshold of 0.1 corresponds to demanding residuals at roughly one tenth of the prior noise level. If a re-calibration is considered appropriate, we can implement candidate calibrations and report their effect on both the reproducer and the recorded case.n_removed/ the inlier fraction next to the coefficients the workflow already prints, warn on extreme retention, optionally a max-rejection / min-inlier policy. Detection, not a cure; a min-inlier cap trades off stopping before the w-test criterion is met.Where the relevant pieces live
crit_value=0.1,max_iterations=50:python/packages/isce3/math/offsets_polyfit.py#L192-L199; stop test and removal at #L325-L343.critical_valuefrom the runconfig andmax_iterations=len(data):python/packages/nisar/workflows/rubbersheet.py#L169-L177.critical_value: 0.1:share/nisar/defaults/insar.yaml#L609; the schema constrains it only asnum(min=0)(share/nisar/schemas/insar.yaml#L602).Code excerpts (pinned at
2919e1c97)How we found it (recorded production comparison)
This is not hypothetical: we first hit the mechanism as a ~3.6e-2 px RMS difference between the CPU and the GPU InSAR workflow products on a NISAR L-SAR pair, and a controlled replay of the production fits attributed that difference to exactly one such quantum flip. In the extracted sample sets, ~99.97% of the 40,000 sample rows were bit-identical between the CPU and GPU runs, and every difference was at least one quantum. The full report, the recorded artifacts and the replay harness are public: report · artifacts · replay/probe harness.
The synthetic reproducer above is independently runnable by anyone. The real-data replay results are public recorded evidence: the source rasters and the extracted sample matrices are not published; we can archive the extracted matrices in a DOI-backed repository (e.g. Zenodo) after confirming redistribution and citation metadata.
Recorded 40k replay: transplanting one sample row reproduces the logged coefficient delta
Dataset context: ascending track 139, frame 019 (L1 RSLC), processed once with the CPU and once with the GPU InSAR workflow. The two runs' RIFG
pixelOffsetslayers differ by a smooth ~3.6e-2 px RMS degree-2 bowl; a linear regression on the offsets difference explains ~99.5% of the unwrapped phase-difference variance between the runs.Sample-set counting: two independent CPU-Ampcor runs were each compared against the GPU-Ampcor baseline on the production sample grid (run A: 10 of 40,000 rows differ; run B: 9 rows; the union is 12 changed rows, 7 common to both).
All replay fits are CPU-only and thread-count controlled; A/A reruns are bit-identical.
corr_peak) forks at iteration 468 and re-converges benignly (2.4e-9 px)corr_peak0.9485, azimuth offset differing by exactly -1/32 px, no range difference) — necessary (complement transplants change the coefficients by exactly zero) and sufficient (the single transplant = full target) within the observed difference sets; the other 11 rows of the 12-row union, including a 5.6/13.9 px (azimuth/range) outlier row, change nothingThe endgame-membership figures are recorded in the membership summary JSON in the artifact directory; the remaining table entries map to the replay/probe JSONs there.
Scope and limitations
Further limitations
corr_peakdistribution, not a quantitative match.Appendix: docstring mismatch in the same module
The
polyfit_offsetsdocstring describesminL/maxL(minP/maxP) as bounds on acceptable offsets ("Points withdLoutside[minL, maxL]are removed before fitting") andremoved_indicesas including points removed "by hard bounds". The implementation uses these parameters only as coordinate-normalization bounds for the design matrix (defaulting to the data's line/pixel coordinate range); no pre-fit removal by offset bounds exists. Happy to fold a docstring fix into whatever comes out of the discussion, or file it separately.Disclosure: The diagnosis and harness were written with assistance from AI agents. I ran the diagnostic scripts myself and reproduced the reported numbers before posting.