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
yTopattribute. (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 tophysicalData.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: passconstants=physicalData.CODATA1986(the frozen, test-pinned verification mode) when reproducing them. The dominant difference is G (+2.6×10⁻⁴ relative); see thephysicalDatadocstring. 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. EY92defaults toseriesOrder=4with corrected Appendix coefficients: passseriesOrder=1(andconstants=CODATA1986) to reproduce Mathematica jleGroup curves, andseriesVariant="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: passtwoLimb=Truefor the far limb / central flash, and setsurfaceRadiuswith 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) [MathematicalambdaHiconvention; λ_true = λ_iso − (3a+5b)/2 applied internally] or (r_h, H) is what the curve constrains.EPQ03inverts 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; passnu_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 boundaryi_bexplicitly (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=Truemakes 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_countsreports 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file jlegroup-1.2.0.tar.gz.
File metadata
- Download URL: jlegroup-1.2.0.tar.gz
- Upload date:
- Size: 343.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f2ebba996987aa4e03906bdaf99c5990f3a9efac3ca28690971a82b640a9df76
|
|
| MD5 |
f93d8e662c6a46146a1fe062dc0d4e3b
|
|
| BLAKE2b-256 |
8c918078d7cd20d9a805dbd0df21495f0c7d94345aeb0cf922c985f75761c0d9
|
Provenance
The following attestation bundles were made for jlegroup-1.2.0.tar.gz:
Publisher:
publish.yml on mjperson/jlegroup
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
jlegroup-1.2.0.tar.gz -
Subject digest:
f2ebba996987aa4e03906bdaf99c5990f3a9efac3ca28690971a82b640a9df76 - Sigstore transparency entry: 2631242030
- Sigstore integration time:
-
Permalink:
mjperson/jlegroup@0100a698d4d581a60fb3c75063bf9dd9b3a2d9a9 -
Branch / Tag:
refs/tags/v1.2.0 - Owner: https://github.com/mjperson
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0100a698d4d581a60fb3c75063bf9dd9b3a2d9a9 -
Trigger Event:
push
-
Statement type:
File details
Details for the file jlegroup-1.2.0-py3-none-any.whl.
File metadata
- Download URL: jlegroup-1.2.0-py3-none-any.whl
- Upload date:
- Size: 157.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f17554a571e737ea87c70166a97e3601fd9b3b19ee05ffac1c6323b0c9e56e17
|
|
| MD5 |
eb3ec8ed6b43806989299070687ac467
|
|
| BLAKE2b-256 |
1724ce640e31e032aa935f34b23ee205c384d9bad890a2026642ec5715d355f6
|
Provenance
The following attestation bundles were made for jlegroup-1.2.0-py3-none-any.whl:
Publisher:
publish.yml on mjperson/jlegroup
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
jlegroup-1.2.0-py3-none-any.whl -
Subject digest:
f17554a571e737ea87c70166a97e3601fd9b3b19ee05ffac1c6323b0c9e56e17 - Sigstore transparency entry: 2631242065
- Sigstore integration time:
-
Permalink:
mjperson/jlegroup@0100a698d4d581a60fb3c75063bf9dd9b3a2d9a9 -
Branch / Tag:
refs/tags/v1.2.0 - Owner: https://github.com/mjperson
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0100a698d4d581a60fb3c75063bf9dd9b3a2d9a9 -
Trigger Event:
push
-
Statement type: