Skip to content

Add astroquery.linelists.exomol — ExoMol database query module#3536

Open
aditya-pandey-dev wants to merge 26 commits intoastropy:mainfrom
aditya-pandey-dev:feature/exomol-linelists
Open

Add astroquery.linelists.exomol — ExoMol database query module#3536
aditya-pandey-dev wants to merge 26 commits intoastropy:mainfrom
aditya-pandey-dev:feature/exomol-linelists

Conversation

@aditya-pandey-dev
Copy link

@aditya-pandey-dev aditya-pandey-dev commented Feb 21, 2026

Implements astroquery.linelists.exomol module for querying the
ExoMol molecular line list database (https://www.exomol.com).

Closes radis/radis#479
Related: radis/radis#925

Changes:-

  • ExoMolClass(BaseQuery) with 4 methods:
    • query_lines() — fetch line lists as astropy.Table
    • get_molecule_list() — list all 91+ molecules
    • get_databases() — list databases per molecule
    • get_partition_function() — fetch Q(T) data
  • broadening_species support (H2, He, air, CO2, H2O, self)
  • 9/9 tests passing (5 mocked + 4 remote_data)
  • RST documentation added

Implements astroquery interface for ExoMol molecular line list database.
Wraps RADIS ExoMol reader (radis.io.exomol) into BaseQuery pattern.

Features:
- query_lines() — fetch line lists as astropy.Table
- get_molecule_list() — list all 91+ ExoMol molecules
- get_databases() — list available databases per molecule
- get_partition_function() — fetch Q(T) data
- broadening_species support (H2, He, air, CO2, H2O, self)

Tests: 9/9 passing (5 mocked + 4 remote_data)

Related: radis/radis#925
@codecov
Copy link

codecov bot commented Feb 22, 2026

Codecov Report

❌ Patch coverage is 46.26866% with 36 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.57%. Comparing base (12d9357) to head (5618a38).
⚠️ Report is 15 commits behind head on main.

Files with missing lines Patch % Lines
astroquery/linelists/exomol/core.py 43.75% 36 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3536      +/-   ##
==========================================
- Coverage   72.66%   72.57%   -0.09%     
==========================================
  Files         219      222       +3     
  Lines       20478    20547      +69     
==========================================
+ Hits        14880    14913      +33     
- Misses       5598     5634      +36     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor

@keflavich keflavich left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this PR! I've wanted clean access to exomol for many years and never got around to writing it myself (...i think because the server wasn't mature yet?).

The implementation looks OK right now, but I have questions: Can this be refactored to avoid pandas and radis? Should it?

There are some minor code issues, and I haven't systematically reviewed the tests, but broadly it looks OK.

Comment on lines +89 to +90
from radis.api.exomolapi import get_exomol_database_list
from radis.api.exomolapi import get_exomol_full_isotope_name
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're introducing a new dependency here, which I don't love doing - but if it's necessary, we'll allow it.

These methods look fairly simple and maybe safe to reimplement here, but if they're heavy methods in any way, it's OK to leave them in radis. I'll invite @bsipocz to comment on this too.

xfail_strict = true
remote_data_strict = true
addopts = ["--color=yes", "--doctest-rst", "--doctest-continue-on-failure"]
timeout = 300
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this removed?

load_wavenum_min=2000,
load_wavenum_max=2100,
)
print(result)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the results should be shown here (and in the other doctest examples)

@aditya-pandey-dev
Copy link
Author

Hi @keflavich I've addressed all your review comments:

  1. Removed pandas from tests (using pytest.importorskip only)
  2. Restored pytest-timeout in pyproject.toml (lines 39 and 121)
  3. Added radis + pandas dependency note clearly in docs
  4. Added example output to all doctest examples in exomol.rst
  5. Kept radis dependency for get_exomol_database_list and get_exomol_full_isotope_name as reimplementing would duplicate significant RADIS logic
    All 9 CI checks are now passing (including Linux ARM ).

Could you please re-review

@keflavich
Copy link
Contributor

Could you say what it would take to remove RADIS as a dependency? Adding it as a dependency creates a circular dependency (https://github.com/radis/radis/blob/be14d4b101b9f068ea0ee04b4e902d57049ea813/environment.yml#L17), so it would be better if astroquery could stand alone.

@aditya-pandey-dev
Copy link
Author

aditya-pandey-dev commented Feb 25, 2026

Hi @keflavich I go through what it would take to remove RADIS as a dependency:

It is comparatively easy to reimplement without radis:

get_exomol_database_list — simple BeautifulSoup HTML scraper (30 lines)
get_exomol_full_isotope_name — static isotope lookup dict (5 lines)

Very complex to reimplement:

fetch_exomol (352 lines) — handles HDF5 file download, caching, line intensity calculations, isotopologue filtering, and broadening parameters. Reimplementing this would essentially duplicate a major portion of RADIS.

I can remove the two helper functions from RADIS and reimplement them natively. But for fetch_exomol, the options are:

a) Keep RADIS as optional dependency (skip tests if unavailable)
b) Reimplement fetch_exomol from scratch — significant effort but removes circular dependency completely
c) Drop query_lines() / get_partition_function() for now and only expose get_molecule_list() and get_databases() without RADIS

What would you prefer?

@keflavich
Copy link
Contributor

Thanks @aditya-pandey-dev . I think the former functions, including the bs4 scraper, belong here.

for fetch_exomol, that sounds like a more involved calculation - @bsipocz I'm loosely inclined to allow this function to use exomol. It's not purely database work, I think, but a little calculation on top. Are you OK with including radis only as a dependency for this function - i.e., import within the function?

Comment on lines +33 to +38
Query CO lines between 2000-2100 cm^-1::

from astroquery.linelists.exomol import ExoMol
result = ExoMol.query_lines('CO',
load_wavenum_min=2000,
load_wavenum_max=2100)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To follow the astroquery standard for APIs, these should be specified with units - so load_wavenum_min=2000*u.cm**-1, for example. Also, the keyword name should be wavenum_min - load doesn't fit here, afaict.

Comment on lines +101 to +102
load_wavenum_min=None,
load_wavenum_max=None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see above

Suggested change
load_wavenum_min=None,
load_wavenum_max=None,
wavenum_min=None,
wavenum_max=None,

Minimum wavenumber in cm^-1.
load_wavenum_max : float, optional
Maximum wavenumber in cm^-1.
broadening_species : str or list of str, optional
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these should be Quantitys

"pytest>=7.4",
"pytest-doctestplus>=1.4",
"pytest-timeout",
"pytest-timeout",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whitespace

@aditya-pandey-dev
Copy link
Author

Hi @keflavich all your review comments have been addressed:

Renamed load_wavenum_min/max → wavenum_min/max with ~astropy.units.Quantity support
get_databases() reimplemented natively with BeautifulSoup4 scraper — no RADIS dependency
RADIS lazy-imported only inside query_lines() and get_partition_function()
pyproject.toml whitespace fixed, timeout = 300 restored

Could you please re-review?

Copy link
Contributor

@keflavich keflavich left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a few more things to clean up in tests now

Comment on lines +15 to +19
try:
import radis # noqa: F401
except ImportError as e:
pytest.skip(f"radis required for exomol tests: {e}",
allow_module_level=True)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

radis is no longer required for most tests, so let's switch this global skip to skipif RADEX_NOT_AVAILABLE down below

monkeypatch.setattr(
"radis.io.exomol.fetch_exomol", lambda *a, **kw: fake_linelist_df
)
result = ExoMol.query_lines("CO", wavenum_min=2000, wavenum_max=2100)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these should now require Quantities (and this test should not be skipped)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, actually, this test should be skipped if radis is unavailable, sorry - but still, these should be quantities

monkeypatch.setattr(
"radis.io.exomol.fetch_exomol", lambda *a, **kw: fake_linelist_df
)
result = ExoMol.query_lines("CO", wavenum_min=2000, wavenum_max=2100)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

quantities

Comment on lines +105 to +106
wavenum_min=1000,
wavenum_max=1100,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

quantities

Comment on lines +155 to +156
wavenum_min=2000,
wavenum_max=2100,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

quantities

Comment on lines +173 to +174
wavenum_min=2000,
wavenum_max=2050,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

quantities

# ===========================================================


@pytest.mark.remote_data
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all the remote_data-flagged tests should go into test_exomol_remote - it makes debugging and testing a lot easier when the non-remote tests and remote-requiring tests are separated

@aditya-pandey-dev
Copy link
Author

Hi @keflavich I've addressed all the review comments:

Replaced global pytest.skip with RADEX_NOT_AVAILABLE flag + @pytest.mark.skipif on each test
Added u.cm**-1 Quantities to all wavenum_min/wavenum_max args
Moved all @pytest.mark.remote_data tests to test_exomol_remote.py
6/6 tests passing locally

Could you please re-review?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Consider upstreaming exomol access to astroquery

3 participants