Skip to main content

jlegroup

Python implementations of the MIT Elliot-group stellar-occultation light-curve methods.

Named for James Ludlow Elliot (1943–2011) — an author of the methods implemented here, and mentor to this package's maintainer.

Module Method Status
jlegroup.CE97 Chamberlain & Elliot (1997), PASP 109, 1170 — numerical light curves from an arbitrary atmospheric model ✅ implemented & validated
jlegroup.EY92 Elliot & Young (1992), AJ 103, 991 — analytic small-planet model with haze, two-limb/central flash, surface cutoff, and the traditional half-light fitting parameterizations (r_h, λ_iso) / (r_h, H) ✅ implemented & validated
jlegroup.EPQ03 Elliot, Person & Qu (2003), AJ 126, 1041 — light-curve inversion & atmospheric retrieval with error propagation, plus optional ratchet binning (concept: W. Saunders) for honest deep-profile statistics on low-S/N curves ✅ implemented & validated
jlegroup.physicalData physical constants in two test-pinned vintages — CODATA-2022/SI (the default, for current research) and CODATA-1986 (the Mathematica jleGroup`physicalData verification vintage), switched per model via constants= — plus the EY92 Table 9 gas registry, multi-gas dispersion formulas (N₂, H₂, Ar, CO₂, He, 85/15 H₂–He "Uranus"), and an occultation-target body registry (Earth, Pluto, Triton, Sun)
jlegroup.shadowmap occultation shadow maps — the Earth seen from the occulting body with coastlines, night shading, fundamental-plane track bands and ground paths, plus the smDist/smOffset prediction workflow (functionality of Mathematica jleGroup`shadowMap 4.1.4; no reference publication). Optional: pip install "jlegroup[shadowmap]" (astropy); loads lazily ✅ implemented & validated

Naming convention: all-lowercase jlegroup is this Python package; camelCase jleGroup refers to the original Mathematica package family used within the group.

Status

Public, MIT-licensed — with permissions granted by all authors in the code lineage (W. Tubthong → W. Saunders → M. Person). v1.0.0 is the first public release; actively maintained. Bug reports and contributions are welcome — see CONTRIBUTING.md.

Install

pip install jlegroup

# shadow maps are an optional extra (adds astropy; the light-curve
# modules work without it):
pip install "jlegroup[shadowmap]"

# latest development version, from GitHub:
pip install git+https://github.com/mjperson/jlegroup.git

# or, for development, from a checkout:
pip install -e ".[test,shadowmap]"

Requires Python ≥ 3.10 and numpy ≥ 2.0.

Quickstart

import numpy as np
from jlegroup import CE97, physicalData

# refractivity profile from a number-density profile n(r) [m^-3], radius in km
nu = physicalData.refractivity(n, gas="N2", wavelength_um=0.7)

model = CE97.ChamberlainElliot1997Model(
    refractivityProfile=nu,
    radialDistance=radius_km,          # increasing, km
    planetDistance=30 * physicalData.AU_KM,   # observer distance, km
    position=y_km,                     # observer-plane positions, km
)
model.main()
flux = model.focusedFlux               # normalized light curve at `position`

See examples/isothermal_occultation.py for a complete, runnable comparison against a bundled reference light curve.

📓 Tutorials (in examples/; shipped executed, with figures):

notebook covers
00_jlegroup_overview.ipynb the package in one exercise: EY92 ↔ CE97 forward cross-check, then the EPQ03 inversion round trip
01_EY92_basics.ipynb analytic model: parameters, series order & misprint corrections, haze, two-limb central flash, traditional fitting parameterizations
02_CE97_basics.ipynb numerical model: atmosphere builders, time-domain curves + noise, validation vs bundled references, the above-atmosphere vacuum clamp
03_EPQ03_basics.ipynb inversion: noiseless round trip, noisy retrieval, the EPQ03 error budget, thermal gradients, ratchet binning
04_shadowmap_basics.ipynb shadow maps on the 2015-06-29 Pluto event: globe / mercator / equirectangular views, night shading, track bands + ground paths, smDist/smOffset (needs the shadowmap extra)

Validation

The CE97 implementation is validated against independently generated reference light curves (Mathematica jleGroup olcOneLimb2, EY92 family; λ = 0.7 µm; N₂; 30 AU; b = 900 km; v = 25 km/s). These run as the pytest regression suite (tests/). All validation comparisons run in the references' CODATA-1986 vintage (constants=physicalData.CODATA1986, pinned in the tests); the package's runtime default is CODATA-2022 — see Gotchas.

Case Temperature profile max |model − ref|
iso-clear T ∝ r⁰ (isothermal, 114.5 K) 4.2 × 10⁻⁵
shallow-clear T ∝ r⁻⁰·⁵ 9.7 × 10⁻⁵
steep-clear T ∝ r⁻⁴·⁵ 1.1 × 10⁻³ *

* Attributed to the reference's first-order-in-1/λ EY92 truncation, not to CE97: the neglected O(λ⁻²) dθ term has coefficient (9 − 34b + 25b²)/128, which quantitatively predicts the observed miss. For b = 0 and −0.5 the agreement meets the CE97 paper's ~10⁻⁴ accuracy claim.

Method cross-validation (EY92 ↔ CE97 ↔ references): the analytic EY92 module validates independently against the paper's own benchmark tables (print precision at all series orders; two misprints in the published Appendix identified — see its module docstring), reproduces the Mathematica references above to ≤ 2.5 × 10⁻⁸ at seriesOrder=1 (the generator's own truncation order), and agrees with CE97 pointwise to ≤ 1.6 × 10⁻⁵ at its default seriesOrder=4 — confirming that the steep-clear deviation in the table is entirely reference truncation. Two-limb (central-flash) fluxes are cross-validated per limb against CE97 evaluated at ±ρ on the same refractivity profiles: ≤ 3 × 10⁻⁷ (λ ≈ 21) and ≤ 7 × 10⁻⁸ (the bundled Mathematica-exact table, λ = 30, checked at merge review).

Inversion (EPQ03): validated against the paper as oracle — the noiseless standard case reproduces Table 3's printed digits (mean/convergence temperature 79.99685 / 79.99797 K vs the printed 79.997 / 79.998; max residual 0.0046 K vs ~0.004), the nonisothermal series stays within Fig. 6's envelope (worst case b = 9: 0.483% vs the printed +0.48%), and the formal errors match Monte-Carlo scatter at (S/N)_H = 100. Independently cross-checked at merge review against the Mathematica-generated iso-clear reference (external to the module's development): the boundary fit recovers the generator's parameters to machine precision (fit rms 8 × 10⁻¹⁴) and the inversion returns the machine-exact input atmosphere to 0.02% in temperature in the converged region, with pressure/density deviations collapsing from ~10⁻³ to ≲ 2 × 10⁻⁴ as the inversion boundary moves below the chord's coarsely sampled region — the binning sensitivity the paper's error analysis predicts.

Known behavior / gotchas

  • Positions above the modeled atmosphere top are vacuum — and clamped (v0.11.0). The model sets flux = 1 exactly (and θ = 0) beyond yTop = r_top + D·θ(r_top) — the above-atmosphere vacuum clamp, boundary exposed as the yTop attribute. (Previously the y→r spline extrapolated there, producing spurious caustic spikes; user-side clamping is now a harmless no-op.) Two things remain yours: build the atmosphere table well above the flux-recovery altitude — a truncated top leaves a ~10⁻³ spline edge artifact within ~2 scale heights below yTop, which no clamp can cure — and note the deep end is deliberately not clamped (below the table there is no unique physical answer).
  • The model sums all geometric images found by the y→r mapping; the bundled references are one-limb curves (far-limb flux is negligible for the bundled geometry).
  • Constants come in two vintages, switched by constants=. Every model defaults to physicalData.CODATA2022 (current values, several SI-exact) — research use gets current physics. The validation references and paper tables were generated at the Mathematica package's CODATA-1986 vintage: pass constants=physicalData.CODATA1986 (the frozen, test-pinned verification mode) when reproducing them. The dominant difference is G (+2.6×10⁻⁴ relative); see the physicalData docstring. Measured data (Table-9 refractivities, dispersion formulas, BODIES) do not participate in the switch. EY92's traditional half-light classes are constants-free by construction.
  • EY92 defaults to seriesOrder=4 with corrected Appendix coefficients: pass seriesOrder=1 (and constants=CODATA1986) to reproduce Mathematica jleGroup curves, and seriesVariant="as-printed" for the journal-text coefficients (~10⁻⁶ flux effect). Its haze path is validated against the paper tables only. The model is one-limb by default: pass twoLimb=True for the far limb / central flash, and set surfaceRadius with it — the transparent analytic atmosphere otherwise passes far-limb rays arbitrarily deep. Sampling is instantaneous (no ExpTime event-bin integration) — extend before fitting real events with haze cut-ons or surface contact. For fitting, use the traditional half-light classes (ElliotYoung1992ModelTraditional, ...TraditionalScaleHeight): physical inputs enter the model only through (ν₀, λ_g0), so fitting P/T/M chases a degeneracy ridge — the half-light pair (r_h, λ_iso) [Mathematica lambdaHi convention; λ_true = λ_iso − (3a+5b)/2 applied internally] or (r_h, H) is what the curve constrains.
  • EPQ03 inverts one limb of a clear atmosphere (haze/extinction and large-body adaptations are out of scope; flux normalization is the caller's responsibility and dominates the systematics — EPQ03 Sec. 7.4). Deliberate deviations from the printed paper are documented in the module docstring: the half-light refractivity default follows EY92 Eq. (4.28) — the paper's own digits require it; pass nu_h_method="epq03-66" for the printed Eq. (66) — Table 10's Loschmidt exponent misprint is corrected, and boundary conditions with λ_h ≲ 6 are rejected (the half-light relation has no real solution there). On noisy data choose the inversion boundary i_b explicitly (Sec. 7.3).
  • On low-S/N inversions, prefer ratchet_binning=True — the default (off) silently biases the deep profile hot. EPQ03's published positivity averaging lets the noise realization set the deep binning, which structurally deletes the lower tail of the noise distribution: deep temperatures come out biased hot by several formal sigmas (≈ +5σ, tens of K, at (S/N)_H = 20) with scatter tighter than the propagated errors — spurious stability, the hot deep bend of the paper's own Fig. 9. ratchet_binning=True makes the positivity bin size a monotone ratchet that tracks the S/N envelope instead, restoring honest deep statistics at the cost of resolution at the lowest flux levels (InversionResult.bin_counts reports the delivered resolution). It is off by default on purpose — the published scheme is the validated baseline and an exact no-op on clean (all-positive) data — but for real low-S/N retrievals you generally want it on (concept: W. Saunders).

Lineage & citation

Original Python implementation of CE97 by Wata (Chanita) Tubthong (ORCID 0000-0002-7907-2634, 2021), extended by William Saunders (ORCID 0000-0002-8737-742X, 2021), edited, packaged, validated, and maintained by Michael J. Person (ORCID 0000-0003-0000-0572, 2026). Development history and the full validation campaign live in the maintainer's research repository.

License

MIT (see LICENSE; all lineage-author permissions granted — see Status).

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jlegroup-1.0.0.tar.gz (280.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

jlegroup-1.0.0-py3-none-any.whl (106.7 kB view details)

Uploaded Python 3

File details

Details for the file jlegroup-1.0.0.tar.gz.

File metadata

  • Download URL: jlegroup-1.0.0.tar.gz
  • Upload date:
  • Size: 280.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for jlegroup-1.0.0.tar.gz
Algorithm Hash digest
SHA256 e4a5e98ff2334b0a876999a6f0d620f81c8b8d99e38e9c80f22ecad900c26478
MD5 df5cc39f49656e3bd3d731648503abfe
BLAKE2b-256 25effb2b7952f8d5a0a31b181c6c37b298a168055e0591ecc76e65b3cfa258f5

See more details on using hashes here.

Provenance

The following attestation bundles were made for jlegroup-1.0.0.tar.gz:

Publisher: publish.yml on mjperson/jlegroup

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file jlegroup-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: jlegroup-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 106.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for jlegroup-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4c9f7c77c6a2cfacf7f04eb4bea945e5486d5189e0fa9ff048a8209d208bbf91
MD5 4830de9cf508c86c43c896014d49e7d5
BLAKE2b-256 6f5f44c6ae2e235f785686dd3dc1eedd9b59464f31f8c9b16c3dc3303f0e92e2

See more details on using hashes here.

Provenance

The following attestation bundles were made for jlegroup-1.0.0-py3-none-any.whl:

Publisher: publish.yml on mjperson/jlegroup

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

1.2.0

2 files

1.1.0

2 files

This release

1.0.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page