Repository navigation
Add utu.dem.emission_measure, the emission measure each temperature of a piecewise-linear DEM stands for - #15
Merged
Merged
Conversation
… of a piecewise-linear DEM stands for plowman fits the intensities with R^T M D, where M is the mass matrix of the triangle functions the DEM and the responses are interpolated with. emission_measure returns M D, so a response times it, summed over temperature, is the intensity the fit found, and it can be given to synthesize as the emission measure of each bin. It moves esis's private _emission_measure here, takes uneven grids and uncertain DEMs, and checks its axis and temperatures. The docstrings of plowman and synthesize point to it. Its own says it is not the integral over a range of temperatures, which FunctionArray.integrate gives, and that it integrates the interpolated functions rather than the functions themselves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017xqpht97bF4FWsACAA4wQa
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #15 +/- ##
==========================================
Coverage 100.00% 100.00%
==========================================
Files 19 21 +2
Lines 1285 1414 +129
==========================================
+ Hits 1285 1414 +129 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…nd correct its docstring From the xhigh code review of #15: - Raise for temperatures that are not positive, which gave NaN and inf, or the wrong message, and for outputs whose axis of temperature does not match the inputs, which a length-1 axis broadcast past. - Accumulate the result in place: 2 times the size of the result at most, rather than 5. - The docstring gives the weights of the two ends, says the sum times the step and the trapezoid rule move the off-diagonal terms of M onto its diagonal rather than leave them out, and shows how to integrate the DEM over a range of temperatures. - It also says how much linear interpolation costs: in its own example, emission_measure gives an intensity 6% above the integral of the two Gaussians, which the plain sum matches, and the example shows both. - Tests of per-pixel grids, uncertain temperatures and the new errors, and the chi2 test simulates its intensities from the functions themselves, as the docstring advises. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017xqpht97bF4FWsACAA4wQa
_mass gives the diagonal and off-diagonal of the mass matrix of the triangle functions, with the arithmetic of the reference in the same order. emission_measure weights the DEM with them, and plowman's _matrices builds its matrix from them, bit for bit the one it built before on even and uneven grids. A change to the basis now reaches both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017xqpht97bF4FWsACAA4wQa
This was referenced Oct 16, 2026
roytsmart
added a commit
to esis-mission/esis
that referenced
this pull request
Oct 16, 2026
The private _emission_measure of esis.data.synth._scene_dem moved down to utu as utu.dem.emission_measure (sun-data/utu#15, released in utu 0.5.0), which also checks its input and shares its mass matrix with utu.dem.plowman. scene_dem now calls it, and its two unit tests are dropped, since utu tests the function. The two agree to 5e-16 on a random map of DEMs, so the scenes change only by rounding. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017xqpht97bF4FWsACAA4wQa
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.
This adds
utu.dem.emission_measure(dem, axis_temperature), the emission measure each temperature of a piecewise-linear DEM stands for, so that intensities predicted from a DEM ofplowmanare the ones its fit found.Why
plowmantakes the DEM D and each response R to be linear in log T between the temperatures, and fits the intensities with Rᵀ M D, where M is the mass matrix of those triangle functions. Code that predicts an intensity from the DEM afterwards usually sums D × R × step instead, which leaves out the off-diagonal terms of M. Three places do this now:figures/_dem_regions.py. On its four regions, the sum is 0.3% to 1.4% away from Rᵀ M D.esis.data.synth.scene_dem(Add synthetic scenes from the DEM of AIA images, and one for ESIS-II esis-mission/esis#109) wrote its own private_emission_measure, so that the AIA intensities of its synthetic scenes are the ones the fit found. This PR moves that function here.What it does
emission_measurereturns M D: Δ(Dᵢ₋₁ + 4Dᵢ + Dᵢ₊₁)/6 inside an even grid, and Δ(2D₀ + D₁)/6 and Δ(Dₙ₋₂ + 2Dₙ₋₁)/6 at the two ends.ValueErrorif:axis_temperature;synthesize: the result can be given toutu.spectrum.synthesizeas its emission measure per bin.The docstring covers two limits:
Ranges: summed over a range of temperatures, the result isn't the integral of the DEM over that range, because each temperature's weight reaches a step either side of it. The docstring shows the code for that integral:
FunctionArray.integrateover log T, from the i-th temperature to the j-th, whose trapezoid rule is exact for a piecewise-linear DEM (as in Integrate the DEM tutorial's temperature ranges by the trapezoid rule solar-dynamics-observatory#30).Interpolation: it integrates the functions as interpolated linearly, which is off by about Δ²/8 times their curvature, not the functions themselves. For smooth functions the grid resolves, the plain sum of the product times the step is much closer to the true integral:
plowman's docstring example, the plain sum matches their integral to 0.000%, while linear interpolation is 14% high in the channels at 14.9 DN/s and 49% high in the two outer channels, which are nearly dark.So the function is for predicting intensities consistently with
plowman's fit.plowman's example keeps its sum, and the docstring says to simulate test intensities from the functions themselves.The docstrings of
plowman(its returned DEM) andsynthesize(itsemission_measure) now point to the function.Tests
utu/dem/_tests/test_emission_measure.py, 16 tests:plowmanbuilds in_matrices, times the DEM, for every pixel of a 2 × 3 map, to 1e-14.plowman: the χ² recomputed from the function's intensities equals the χ²plowmanreports, to 1e-6. The test simulates its intensities from the functions themselves, as the docstring advises.I checked that the tests can fail. On the final code:
On the first version, the rectangle rule failed 7 of 10. I also checked that
np.allcloseon uncertain arrays compares their samples, and that the DEM passed in is left unchanged. The whole suite passes (124 tests), andblackandruffare clean. All three examples in the docstring run.Code review
An xhigh review found no errors in the math. Its findings, all reproduced and fixed in 3fe870a and 4793426:
The review also pointed out that the mass matrix was written twice, here and in
plowman's_matrices. 4793426 defines it once, in a private_mass, which gives the diagonal and the off-diagonal with the reference's arithmetic in the same order.emission_measureweights the DEM with those two vectors._matricesbuilds its matrix from them, and it is bit for bit the matrix it built before. I checked this on the 0.05-dex grid, on an AIA-like grid and on a random uneven one.Not changed:
plowmanrequires of its responses.After this
utu~=0.5.integrate, the XRT ratio with this function) and for the jet paper's ratio.🤖 Generated with Claude Code
https://claude.ai/code/session_017xqpht97bF4FWsACAA4wQa