From dc7f1bb438c7dd397b875665bf77fab44dfadb99 Mon Sep 17 00:00:00 2001 From: Roy Smart Date: Mon, 19 Oct 2026 19:58:35 -0600 Subject: [PATCH] Quiet the warning of fiasco that an ion is solved with the single-ion model fiasco asks for the two-ion model by default, and solves an ion without the data that model needs, which is most of them, with the single-ion model instead, logging a warning every time. The single-ion model is the right one for those ions, so `contribution_function` now drops that one record from the log of fiasco while it calls fiasco, and nothing else. Passing `use_two_ion_model=False` would change the lines of the ions which do have the data, and telling them apart beforehand needs an API private to fiasco. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017xqpht97bF4FWsACAA4wQa --- utu/spectrum/_lines.py | 27 ++++++++++++++++++++---- utu/spectrum/_tests/test_lines.py | 34 +++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/utu/spectrum/_lines.py b/utu/spectrum/_lines.py index 7671a88..1c2c973 100644 --- a/utu/spectrum/_lines.py +++ b/utu/spectrum/_lines.py @@ -1,6 +1,7 @@ """The emission lines of an optically thin plasma.""" import functools +import logging import astropy.units as u import fiasco @@ -245,10 +246,14 @@ def contribution_function( coupled = tuple(axis_density) == (axis_temperature,) - result = ion.contribution_function( - na.as_named_array(density).ndarray, - couple_density_to_temperature=coupled, - ) + fiasco.log.addFilter(_not_single_ion) + try: + result = ion.contribution_function( + na.as_named_array(density).ndarray, + couple_density_to_temperature=coupled, + ) + finally: + fiasco.log.removeFilter(_not_single_ion) # the last axis of the result runs over the bound-bound transitions, so # that is the wavelength which belongs to it @@ -266,6 +271,20 @@ def contribution_function( ) +def _not_single_ion(record: logging.LogRecord) -> bool: + """ + Whether a record of the log of :mod:`fiasco` is anything but its warning + that an ion is solved with the single-ion model. + + :mod:`fiasco` asks for the two-ion model by default, and solves an ion + without the data that model needs with the single-ion model instead, + which is the right model for it. It warns each time, and that is most + ions, while the only way to tell which ions they are beforehand is + private to :mod:`fiasco`. + """ + return "Using single-ion model" not in record.getMessage() + + def lines( temperature: na.AbstractScalar, density: u.Quantity | na.AbstractScalar, diff --git a/utu/spectrum/_tests/test_lines.py b/utu/spectrum/_tests/test_lines.py index 0eab0de..a902a74 100644 --- a/utu/spectrum/_tests/test_lines.py +++ b/utu/spectrum/_tests/test_lines.py @@ -144,6 +144,40 @@ def test_contribution_function_grid(proton_electron_ratio: u.Quantity): assert shape["_density"] == 2 +@needs_database +def test_contribution_function_single_ion( + caplog: pytest.LogCaptureFixture, + proton_electron_ratio: u.Quantity, +): + """ + An ion without the data for the two-ion model, which fiasco solves with + the single-ion model and warns about, is solved the same way without + the warning. + """ + ion = fiasco.Ion("O 5", temperature.ndarray) + + fiasco.log.addHandler(caplog.handler) + try: + result = utu.spectrum.contribution_function( + ion=ion, + density=density, + axis_temperature="temperature", + proton_electron_ratio=proton_electron_ratio, + ) + assert not any("single-ion" in r.getMessage() for r in caplog.records) + + # fiasco itself warns about this ion, once the call above is done + expected = ion.contribution_function( + density.ndarray, + couple_density_to_temperature=True, + ) + assert any("single-ion" in r.getMessage() for r in caplog.records) + finally: + fiasco.log.removeHandler(caplog.handler) + + assert np.all(result.outputs.ndarray == expected[:, 0, :]) + + @needs_database def test_lines(): """The whole of it, with nothing given that can be worked out."""