Skip to content

TrendFit.make_1dfits() leaves un-fit Gaussian objects in .ffuncs, breaking downstream .popt/.TeX_info access #434

Description

@blalterman

Summary

TrendFit.make_1dfits() is meant to remove failed 1D fits from self.ffuncs
(moving them to self.bad_fits), so that every fit object remaining in
self.ffuncs afterward has had make_fit() succeed on it. In practice, some
fit objects survive in self.ffuncs without a successful fit ever having
completed on them — they lack _popt/_TeX_info — which crashes any code
that assumes make_1dfits()'s filtering is exhaustive.

Environment

  • solarwindpy 0.3.1.dev22+gb4fff2e2 (commit b4fff2e, master, 2026-07-23)
  • pandas 2.2.2, Python 3.12.2, macOS

Observed behavior (verified)

Running a per-heliocentric-distance-bin trend-fit loop (fitting a Gaussian
per solar-wind-speed bin, then a HingeSaturation trend across those bins)
against real New Horizons/SWAP PUI data:

tf.make_ffunc1ds(**fit_1d_kwargs)
tf.make_1dfits(return_exception=True)

for kk, v in tf.ffuncs.items():
    v.TeX_info.set_TeX_argnames(mu=r"\mu", sigma=r"\sigma")   # <-- AttributeError here
    ...

raises:

AttributeError: 'Gaussian' object has no attribute '_TeX_info'

Adding a defensive check confirmed the same objects also lack _popt:

AttributeError: 'Gaussian' object has no attribute '_popt'

Inserting a diagnostic filter directly after make_1dfits():

_unfit = [k for k, v in tf.ffuncs.items() if not hasattr(v, '_popt')]
if _unfit:
    print(f"Dropping {len(_unfit)} fits that never populated _popt: {_unfit}")
    tf.ffuncs.drop(_unfit, inplace=True)

confirmed, per r-bin, that 2-4 Gaussian objects out of ~20 vsw-bin fits were
left in tf.ffuncs in this broken state, and dropping them let the rest of
the pipeline (trend fit, plotting) complete normally. This is a reliable,
reproducible workaround, applied locally (not upstreamed here) at
https://github.com/blalterman/nh/commit/c4a6fc1.

What I verified vs. what I believe

  • Verified: TrendFit.make_1dfits's own filtering logic (fit_success.dropna()
    isolating None-returning fits from exception-returning fits in an
    object-dtype pandas Series) behaves correctly in isolation — a small
    standalone pd.Series([None, ValueError('x'), None]) test drops exactly
    the exception entry.
  • Verified: with real data, some fits that are not caught by that
    filtering nonetheless never call build_TeX_info()/set _popt (i.e.
    make_fit() did not complete its success path for them), yet they are not
    present in tf.bad_fits either.
  • I believe (not confirmed) this points to a fit failure mode that isn't
    routed through either of make_fit()'s two guarded try/except blocks
    (around _run_least_squares and around the initial sufficient_data
    check) — e.g. an exception raised during
    _calc_popt_pcov_psigma_chisq(res, p0), which sits outside both guarded
    blocks. I was not able to isolate a minimal synthetic reproduction in the
    time available (a synthetic sparse-bin TrendFit with 2/40-point columns
    did not reproduce the issue), so this is an unconfirmed theory, not a
    diagnosed root cause.

Suggested next step

Instrument make_fit()'s success path (or TrendFit.make_1dfits) to catch
and record any exception (not just the two currently-guarded exception
tuples) into bad_fits, so self.ffuncs is guaranteed self-consistent
(every remaining entry has _popt/_TeX_info) regardless of which stage of
fitting failed.

Reproduction path

I don't yet have a minimal standalone repro, but the failure is fully
reproducible against real PUI data via ahe_vsw.ipynb's per-r-bin trend fit
cell in https://github.com/blalterman/nh (commit prior to
c4a6fc1 reproduces it; that commit adds the local workaround). Happy to share
the specific data file/bin if useful for reproduction.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions