Problem
StructureFunction2D.fit_spiral (structurefunction.py:2066) raises ValueError
when a ring has fewer than len(modes) + 1 finite azimuthal S_2 points
(line ~2101: "Not enough finite points to fit {} modes."). StructureFunction2DStack.fit_spiral
(structurefunction.py:2852) loops over self.results calling this per-ring with no
try/except, so one masked/incomplete annulus aborts the whole stack fit rather
than just that ring. calculate_modal_power (structurefunction.py:2882) inherits
the same gap, since it calls self.fit_spiral directly.
This is exactly the kind of thing real interferometric data hits: masked pixels,
partial azimuthal coverage at the inner/outer edge of the analysis annulus, etc.
Clean, full-coverage synthetic fields (the only case in test_structurefunction.py
today) never trigger it, so it went unnoticed.
Context: calculate_modal_power had no in-repo caller until now (see
a00a695's commit message on fix-modal-power-unpack, which fixed an unrelated
tuple-unpacking bug in the same method). It now has a real one -- Paper 2's
"Modal Power" analysis (StructureFunction/paper2/notebooks/run_s2_modal_power.py)
-- which is what surfaced this.
Suggested fix
Catch the per-ring failure in StructureFunction2DStack.fit_spiral (and by
extension calculate_modal_power) and fill a NaN row instead of propagating,
matching the censoring-free convention already used elsewhere in this project
(e.g. noise_mc._accumulate_null, spiral_null._SpiralNull.build_null in
StructureFunction/paper2/notebooks): a draw/ring that can't be fit contributes
NaN, not a crash.
Reference implementation
run_s2_modal_power.robust_modal_power in the StructureFunction repo
reproduces calculate_modal_power's reduction with this fix (per-ring
try/except around r.fit_spiral), and has been checked bit-identical to
calculate_modal_power on a fully-covered synthetic stack
(max abs diff frac_of_data == 0.0). Worth pulling that fault-tolerance
into eddy directly rather than leaving it as a local workaround, along with
a test case that exercises a ring with insufficient azimuthal coverage
(currently untested).
Problem
StructureFunction2D.fit_spiral(structurefunction.py:2066) raisesValueErrorwhen a ring has fewer than
len(modes) + 1finite azimuthalS_2points(line ~2101:
"Not enough finite points to fit {} modes.").StructureFunction2DStack.fit_spiral(structurefunction.py:2852) loops over
self.resultscalling this per-ring with notry/except, so one masked/incomplete annulus aborts the whole stack fit rather
than just that ring.
calculate_modal_power(structurefunction.py:2882) inheritsthe same gap, since it calls
self.fit_spiraldirectly.This is exactly the kind of thing real interferometric data hits: masked pixels,
partial azimuthal coverage at the inner/outer edge of the analysis annulus, etc.
Clean, full-coverage synthetic fields (the only case in
test_structurefunction.pytoday) never trigger it, so it went unnoticed.
Context:
calculate_modal_powerhad no in-repo caller until now (seea00a695's commit message on
fix-modal-power-unpack, which fixed an unrelatedtuple-unpacking bug in the same method). It now has a real one -- Paper 2's
"Modal Power" analysis (
StructureFunction/paper2/notebooks/run_s2_modal_power.py)-- which is what surfaced this.
Suggested fix
Catch the per-ring failure in
StructureFunction2DStack.fit_spiral(and byextension
calculate_modal_power) and fill a NaN row instead of propagating,matching the censoring-free convention already used elsewhere in this project
(e.g.
noise_mc._accumulate_null,spiral_null._SpiralNull.build_nullinStructureFunction/paper2/notebooks): a draw/ring that can't be fit contributes
NaN, not a crash.
Reference implementation
run_s2_modal_power.robust_modal_powerin the StructureFunction reporeproduces
calculate_modal_power's reduction with this fix (per-ringtry/except around
r.fit_spiral), and has been checked bit-identical tocalculate_modal_poweron a fully-covered synthetic stack(
max abs diff frac_of_data == 0.0). Worth pulling that fault-toleranceinto eddy directly rather than leaving it as a local workaround, along with
a test case that exercises a ring with insufficient azimuthal coverage
(currently untested).