diff --git a/exotic/api/elca.py b/exotic/api/elca.py index f0fdb421..fd46d680 100644 --- a/exotic/api/elca.py +++ b/exotic/api/elca.py @@ -374,9 +374,17 @@ def transit_duration(values): if not np.isfinite(chord_sq) or chord_sq <= 0 or not np.isfinite(impact_scale) or impact_scale <= 0: return np.nan - argument = np.sqrt(chord_sq) / (impact_scale * sin_inc) + # The arcsin argument is normalized by a*sin(i) (NOT by the eccentricity- + # scaled planet-star separation), and the eccentric orbital-speed factor + # sqrt(1-e^2)/(1+e*sin(omega)) multiplies the whole duration — the same + # correction issue #1383 / PR #1384 applied to the three copies of this + # formula in exotic.py and transit QC; this fourth copy was missed. The + # broken form biased the ns duration prior, the QC duration score, and the + # exposure-smearing window for every eccentric target. + argument = np.sqrt(chord_sq) / (ars * sin_inc) argument = float(np.clip(argument, -1.0, 1.0)) - duration = (period / np.pi) * np.arcsin(argument) + eccentric_speed_factor = np.sqrt(1.0 - ecc ** 2) / max(np.finfo(float).eps, 1.0 + ecc * np.sin(omega)) + duration = (period / np.pi) * np.arcsin(argument) * eccentric_speed_factor return float(duration) if np.isfinite(duration) and duration > 0 else np.nan diff --git a/tests/test_ephemeris_validation.py b/tests/test_ephemeris_validation.py index 9154efe8..97b08308 100644 --- a/tests/test_ephemeris_validation.py +++ b/tests/test_ephemeris_validation.py @@ -90,3 +90,24 @@ def archive_lookup(): {'pName': 'Example b', 'pPer': None, 'midT': 2460000.25}, archive_lookup=archive_lookup, ) + + +def test_elca_transit_duration_matches_model_for_eccentric_orbits(): + """Regression for the fourth site of issue #1383: elca.transit_duration + retained the inverted-eccentricity formula after PR #1384 fixed the three + copies in exotic.py. The analytic duration must match the transit model's + own first-to-fourth-contact duration for eccentric geometries (it was off + by 1.95x at e=0.3/omega=90 and 3.48x at e=0.5/omega=90).""" + import numpy as np + from exotic.api.elca import transit, transit_duration + + for ecc, omega in [(0.0, 90.0), (0.3, 90.0), (0.3, 270.0), (0.5, 90.0)]: + values = dict(u0=0.5, u1=0.1, u2=0.3, u3=-0.1, rprs=0.1, per=3.0, + ars=8.0, tmid=1.0, ecc=ecc, omega=omega, inc=88.0) + times = 1.0 + np.linspace(-0.35, 0.35, 300001) + flux = transit(times, values) + in_transit = np.flatnonzero(flux < 1 - 1e-9) + measured = times[in_transit[-1]] - times[in_transit[0]] + analytic = transit_duration(values) + assert analytic == pytest.approx(measured, rel=0.01), ( + f"e={ecc} omega={omega}: analytic {analytic*24:.4f} h vs model {measured*24:.4f} h")