Distortion-fit machinery: scan engine, time-dependent pointing, and committed fit results - #68
Open
jacobdparker wants to merge 22 commits into
Open
Distortion-fit machinery: scan engine, time-dependent pointing, and committed fit results#68jacobdparker wants to merge 22 commits into
jacobdparker wants to merge 22 commits into
Conversation
…`ConvergenceLogger`, porting the distortion-fit machinery out of the notebook. The hand-built guess/units/bounds lists and positional parameter unpacking from the distortion notebook are replaced by a named parameter prototype flattened with `na.pack`/`na.unpack`. Also added `esis.flights.f1.optics.distortion_fit_bounds()`, recording the bounds of the ESISI_distortion_optimization_20260213_151715 run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wraps `scipy.optimize.differential_evolution` around `DistortionObjective` with packed bounds and initial guess, optional `ConvergenceLogger` output, and returns the best solution as a `DistortionParameters`. Replaces the per-channel optimization loop in the distortion notebook. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…el`. When fitting several channels at once with shared parameters, the correlation and off-target penalty are now computed independently for each channel and averaged, so per-channel brightness differences do not suppress the merit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…he`. Avoids repeating the JSOC queries on every fresh session. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Loads the Level-1 frame and the cached AIA scene, starts from the hard-coded per-channel `distortion_fit()`, couples the physically-shared parameters (field-stop roll, primary displacement, and pointing; 36 -> 21 degrees of freedom since there is only one field stop, one primary, and one payload), and polishes all four channels at once with `fit_distortion()`. `quick = True` runs a minutes-long demonstration fit; set it to `False` for a production fit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…stortion_lsq`, and `fit_distortion_series`. Deterministic derivative-based alternative to the differential-evolution polish, with coarse-to-fine smoothing schedules and warm-started time-series fitting. Not yet validated as an improvement over the DE fit on real data (see distortion_lsq_run); needs a robustness review of parameter normalization and finite-difference step sizes before PR. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ith the modeled image only. Per the 2026-07-06 merit-smoothness scan, a 1-pixel Gaussian on the model image at a 401-pixel scene resolution reduces the minimum readable finite-difference step from 0.21 to 0.07 arcsec, and is the physical choice: the observation already carries the real PSF, so only the raytraced image needs the blur. Passed through `fit_distortion_lsq` and `fit_distortion_series`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ing the fitted per-frame payload pointing. The pointing offsets (one per Level-1 frame, common to all channels) were measured 2026-07-06 by independent per-frame coherent pitch/yaw/roll merit scans against the imaged AIA scene (401-pixel scene, 1-pixel Gaussian PSF on the modeled image, parabola-refined). Yaw dominates, sweeping monotonically from +3.3 to -4.4 arcsec across the flight. Full fit artifacts in pointing_run/ (untracked). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rtion_fit()`. Phase cross-correlation of the modeled and observed images revealed constant per-channel misregistrations (up to ~2.5 detector pixels in y for channels 0 and 2) frozen into the original stochastic 201-pixel optimization. Per-channel peaks read from deterministic PSF-smoothed correlation scans at the time=15 reference frame raise the per-channel correlation to [0.572, 0.604, 0.564, 0.585] from [0.561, 0.603, 0.546, 0.584]. The per-frame time offsets will be refit against this corrected reference in the following commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…)` against the re-polished reference. The time=15 offsets are now consistent with zero (pitch +0.07", yaw -0.005"), confirming the corrected per-channel reference and the time series agree. Mean PSF-smoothed correlation across the flight rises to 0.569 from 0.566. Run 2026-07-07 on rci-parkerlab, 30 frames x 88 evaluations, artifacts in pointing_run/ (untracked). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The vendor measurements report the magnitude of the radius of curvature, but the sag convention for these concave gratings is negative (the design radius is -597.83 mm). As committed, the as-built grating diverged the beam, producing a ~96 mm RMS spot; with the correct sign the model focuses (~0.07 mm RMS). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013gaMo9vwybK8qWZzCR5c3j
…istortionParameters` file I/O. `fit_distortion_scan` is the derivative-free engine proven on the 2026-07 per-frame pointing fits: coarse-to-fine axis scans refined with a parabola, with an optional per-channel separable readout (one coherent scan pass yields every channel's peak). `fit_distortion_series` now drives it, fitting every frame independently from a shared reference (optionally in parallel). `fit_distortion_lsq` is removed: scipy.optimize.least_squares under-converges on the locally-flat smoothed-correlation surface (t=0 of the June series moved 2e-5 arcsec before terminating while the true peak was ~4 arcsec away). `DistortionResidual` remains as the deterministic objective, with a note recording the finding. `DistortionParameters.to_file()`/`from_file()` round-trip fit results through plain-text ECSV tables (units preserved, provenance in the header) so they can be committed and reviewed as text. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013gaMo9vwybK8qWZzCR5c3j
The hardcoded per-channel reference parameters and per-frame pointing offsets now live in `_data/distortion_reference.ecsv` and `_data/distortion_pointing.ecsv`, written by `DistortionParameters.to_file()` with the provenance of each fit in the table headers, and are loaded on demand. Tests pin spot values so the files and their loader cannot drift silently. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013gaMo9vwybK8qWZzCR5c3j
A grids entry keyed by a tuple of field names scans the outer product of the given offset grids and reads the joint peak, which is required for strongly-coupled pairs (grating pitch x instrument pitch, grating yaw x instrument yaw): independent 1-D scans of such pairs converge into a compensating local optimum instead of the true solution, as found when fitting the as-built ESIS-I model from scratch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013gaMo9vwybK8qWZzCR5c3j
…he regeneration entry points for the committed fits. Anyone can now re-derive (or verify) the committed data files from the flight data: `fit_distortion_reference()` fits all nine per-channel degrees of freedom from the as-built model with the joint-scan capture schedule, and `fit_distortion_pointing()` refits the per-frame payload pointing series, each optionally writing the ECSV file it reproduces. `fit_distortion_scan` gained a `coherent` option (per-channel merit, one shared peak) implementing the rigid-payload model of the series. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013gaMo9vwybK8qWZzCR5c3j
The least-squares polish section is replaced with `fit_distortion_scan` (coherent rigid-payload pointing polish), and a closing section documents the committed ECSV fit results, their regeneration entry points, and the model/data blink comparison of the whole flight (docs/_static/blink.gif). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013gaMo9vwybK8qWZzCR5c3j
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
`fit_distortion_series(workers=...)` now uses the spawn start method explicitly: the fork default on Linux is unsafe once numpy/numba thread pools exist in the parent and duplicates its full memory into every worker, which broke the process pool on the CI runners. The flight-data-dependent `_fits` entry points are excluded from coverage (they require the JSOC scene and hours of raytracing), and small tests now exercise the extra-axis reduction, the file-format and joint-grid error branches, the polish-tolerance convergence path, and the default-parameter paths. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013gaMo9vwybK8qWZzCR5c3j
The docs notebooks pay the full Level-1 pipeline (peaks of tens of GiB) inside whichever nbsphinx kernel first calls level_1(), on top of Sphinx's own accumulated memory — enough to OOM the runner now that the distortion-fit tutorial joins the build. Computing the Level-1 and AIA-scene caches in a clean process before Sphinx runs keeps that peak isolated, and persisting ~/.esis/cache with actions/cache makes it a no-op on subsequent builds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013gaMo9vwybK8qWZzCR5c3j
The warm step exhausts the runner where a local measurement of the same call peaks at 24.7 GiB; the ticker records the actual memory trajectory so the failure is diagnosable from the log. The warm-up is also split per level so the failing stage is unambiguous. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013gaMo9vwybK8qWZzCR5c3j
The generic `esis.data.synth.scene_aia` gained a `crop` parameter, applied after the normalization is computed from the full frame but before the radiometric arithmetic, so the kept pixels are identical to cropping the result. The ESIS-I wrapper now passes its central- third crop this way and builds the scene one spectral line at a time instead of opening all three AIA channels at once. The result is bit-identical to the previous implementation (verified against the cached scene); the remaining peak is internal to `sdo.aia.open` for a single channel. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013gaMo9vwybK8qWZzCR5c3j
|
Documentation preview available at https://esis-mission.github.io/esis-pr-previews/68-feature/distortion-fit-machinery |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Distortion-fit machinery: scan engine, time-dependent pointing, and committed fit results
This PR turns the notebook-era distortion fit into library machinery, adds the fitted time-dependent payload pointing of the 2019 flight, and replaces hardcoded fit numbers with committed, human-readable data files that anyone can regenerate from the flight data.
The fitted model
distortion_fit()now loads its per-channel parameters from_data/distortion_reference.ecsv. The reference pointing was re-polished against a deterministic PSF-smoothed correlation merit (401² scene), correcting constant per-channel misregistrations of up to ~2.5 detector pixels left by the original stochastic 201² optimization.distortion_fit(axis_time="time")adds the fitted per-frame payload pointing (one element per Level-1 frame, from_data/distortion_pointing.ecsv). During the flight the payload yaw sweeps monotonically from +3.3″ to −4.4″; pitch and roll drift less. Common-mode registration residuals after this model are ≤0.5 detector pixels across all 30 frames.The fitting engine
esis.optics.fit_distortion_scan()— a derivative-free coordinate search: coarse-to-fine scans of each parameter through the correlation merit, refined with a parabola. Per-channel separable readout (axis_channel) fits all four channels in one pass of coherent scans;coherent=Trueimplements a rigid-payload model; tuple-keyed rounds scan strongly-coupled pairs jointly (grating pitch × instrument pitch, etc.), which independent 1-D scans provably need. Every fit writes its scan curves (scan.json) as an audit trail.fit_distortion_series()now drives the scan engine, fitting every frame independently from a shared reference (parallel over frames withworkers).fit_distortion_lsqis removed.scipy.optimize.least_squaresunder-converges on this merit even with smoothing and tuned steps (at t=0 of the June series it moved 2×10⁻⁵ arcsec before terminating while the true peak was ~4″ away).DistortionResidualremains as the deterministic objective with a note recording the finding; the DE path (fit_distortion) is unchanged.system.image()is stochastic only through the global numpy RNG (cell_centers(random=True)); seeding before each evaluation plus a σ=1 px Gaussian PSF on the modeled image only (sigma_psf) makes the merit deterministic and smooth enough to read at the 0.07″ level.Results as data, not hardcode
DistortionParameters.to_file()/from_file()round-trip fit results through plain-text ECSV (units preserved, provenance in the header), so results diff cleanly in review.esis.flights.f1.optics.fit_distortion_reference()(all 9 per-channel dof from the as-built model, from scratch) andfit_distortion_pointing()(the 30-frame series) reproduce the committed files for verification. The series takes ~17 CPU-hours (≈35 min on a large workstation); merits reproduce to ~10⁻⁴ across machines (threaded-reduction floating point).Bug fix
as_built()had a sign error in the measured grating radii of curvature (+597 mm vs the −597.83 mm design convention), making the as-built grating diverge the beam (96 mm RMS spot). Fixed, with a focus regression test. This is also why the fit machinery previously started fromdesign()rather than the measured model.Docs
docs/reports/distortion-fit.ipynb) replaces its least-squares section with the scan engine and ends with the committed-results/blink summary.🤖 Generated with Claude Code
https://claude.ai/code/session_013gaMo9vwybK8qWZzCR5c3j