Conversation
…pi (2020) A Numba port of `simple_reg_dem` from EMToolKit, run in parallel over pixels, with a named-arrays front end. It reproduces the reference to about 1e-11 relative on AIA data and is ~40x faster on one core; a full 4096^2 AIA image takes ~12 s on 24 cores against ~4 h for the reference. The coverage job runs with NUMBA_DISABLE_JIT so the kernel's lines are seen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XR6qYUm91Mfxnsdgo3tcyg
…ery function The reference floors the intensities of its initial guess at a bare 0.01 in whatever units its data are in. `floor` is now a keyword, 0.01 DN/s by default (the reference's 0.01 DN for a one-second exposure), converted to the units of `intensity`; plain-number intensities need a plain-number floor. Every function, including the tests, is annotated, and pyright reports no errors in `utu.dem`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XR6qYUm91Mfxnsdgo3tcyg
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #9 +/- ##
==========================================
Coverage 100.00% 100.00%
==========================================
Files 13 17 +4
Lines 501 966 +465
==========================================
+ Hits 501 966 +465 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Code review (Claude Code,
|
| Docstring | Assertion | |
|---|---|---|
| Peak position | within a quarter of a decade | < 0.3 dex |
| Emission measure | within 40 percent | rtol=0.5 |
A regression that moves peaks by 0.28 dex or emission measures by 48% still passes.
12. A mismatched temperature count fails deep inside _matrices
Nothing checks that response.outputs has as many temperatures as response.inputs.
- Reproduction: 30 input temperatures with 31 outputs raises "matmul: Input operand 1 has a mismatch in its core dimension 0 ... (size 30 is different from 31)" (verified).
- Suggested fix: raise a clear
ValueErrorhere, like the other shape checks do.
13. A per-channel uncertainty is expanded into a full copy
_ndarray makes every broadcast input contiguous, so a single uncertainty per channel becomes a full (pixel, channel) float64 array before the kernel runs.
- Cost: about 805 MB extra for a 4096^2 x 6 AIA cube.
- Suggested fix: pass the stride-0 broadcast view (numba accepts 'A'-layout arrays), or pass a per-channel errors array.
14. unit or u.dimensionless_unscaled is repeated three times
The pattern appears at L31, L287 and L314-L316, and re-implements na.unit_normalized. na.unit_normalized(intensity) and na.unit_normalized(response.outputs) would give the same result; the None check for unitless output still needs na.unit once.
- The output unit isn't normalized either: an intensity in DN/min gives a DEM in
s / (min cm5).
15. _matrices uses raw numpy instead of named-arrays
The group's guideline is to use named_arrays rather than numpy directly, except inside the packages named-arrays itself depends on (ndfilters, colorsynth, regridding). utu is not one of those. The numba kernel itself has to take ndarrays, but _matrices is ordinary wrapper code built from np.diag, np.concatenate and np.matmul.
🤖 Generated with Claude Code
… inputs - The initial guess checks for a negative ratio itself, since `reassoc` simplifies exp(log(x)) to x and lost its NaN, so compiled pixels with negative responses returned chi2 = NaN rather than -1. - A NaN, zero or negative uncertainty fails the pixel; the reference takes a negative one as positive. - Temperatures must have units, be positive and increasing, at least two, and as many as the responses; `smoothness` and `floor` must be positive; `intensity` itself must have the channel axis, and neither it nor the uncertainty the temperature axis. All checked before inverting. - Units are strict: a plain uncertainty or floor with an intensity in units is an error. `floor` defaults to 0.01 in the units of `intensity`, as in the reference, so DN/(pix s) and counts work. - Uncertain intensities, uncertainties and responses give an uncertain DEM and chi2, each sample inverted as one more pixel. - Tests: the reference copy catches failed factorizations, and new tests cover the fast-math case (it fails on the old kernel), a numerically singular system, the factorization itself, chunks sharing work arrays, units, uncertain inputs, and every validation; recovery bounds now match their docstring. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XR6qYUm91Mfxnsdgo3tcyg
Summary
Adds
utu.dem.plowman(), the regularized differential emission measure (DEM) inversion of Plowman & Caspi (2020). It is a Numba port ofsimple_reg_demfrom EMToolKit, run in parallel over pixels, with a named-arrays front end.demis anna.FunctionArrayover the response's temperatures, in units ofintensityover the units of the response (cm^-5 for AIA).chi2is the reduced chi squared of each pixel, or -1 where the first step failed.If the intensity, uncertainty or response is an
na.UncertainScalarArray, the DEM and chi2 are too: the nominal values are inverted, and each sample of the distribution is inverted as one more pixel.Arguments that can't be inverted are rejected before anything runs. That covers temperatures without units, not increasing, or not matching the responses; an intensity without the channel axis, or with the temperature axis; non-positive
smoothnessorfloor; and an uncertainty or floor in units other than the intensity's.The module is instrument-agnostic: responses and uncertainties come from the caller.
Accuracy and speed
Against EMToolKit itself:
A full 4096 x 4096 six-channel AIA image takes about 12 s on 24 cores, against about 3.6 h for the reference. EMToolKit's process-pool version is no faster than its serial one, because it pickles the whole cube for every pixel.
It cannot agree to the last bit. The regularization matrix is singular on its own, so where the data say little the linear systems are near-singular, and any two Cholesky implementations drift apart over the iteration. A port of the reference with
numpy.linalgin place ofscipy.linalgdisagrees with it by as much. The worst cases are pixels with no signal, at ~1e-6.The kernel allocates nothing inside the iteration, and compiles with only the
contractandreassocfast-math flags. They are worth about a third in speed and move results by ~1e-12;nnanandninfare left off because they would let the compiler delete the checks that decide a pixel has failed.reassocalso simplifiesexp(log(x))tox, which drops the NaN of the logarithm of a negative number, so the initial guess checks for a negative ratio itself.Departures from the reference
floor, the least intensity the initial guess assumes in a channel, is a keyword that can be given in any units. The reference floors at a bare 0.01 in whatever units its data are in. That is the default here too, so it works for DN/s, DN/(pix s), counts or plain numbers.Tests
The tests use no AIA data. Each pixel is independent, so testing needs only responses and intensities shaped like AIA's, and a copy of the algorithm to compare against:
simple_reg_demtranscribed line for line into the test file, withnumpy.linalgin place ofscipy.linalg, so neither EMToolKit nor scipy is a test dependency.They cover:
reassocfix);The comparisons against EMToolKit and on real AIA data above were made outside the suite.
CI
Coverage cannot see inside a compiled function, so the coverage job now sets
NUMBA_DISABLE_JIT=1and runs the kernel as plain Python. The test matrix runs it compiled. Coverage ofutu.demis 100%.Adds
numbaas a dependency, and intersphinx for scipy and numba.🤖 Generated with Claude Code
https://claude.ai/code/session_01XR6qYUm91Mfxnsdgo3tcyg