import os, json, csv
import numpy as np
from calcephpy import CalcephBin, Constants
KDIR = os.environ.get("KERNELS", ".")
OUT = os.path.join(os.path.dirname(__file__), "..", "tests", "fixtures", "inter_ephemeris")
UNIT = Constants.UNIT_KM + Constants.UNIT_SEC + Constants.USE_NAIFID
M = 1000.0
LUNAR_DIST = 3.84e8 JD0 = 2460310.5 DAYS = np.arange(0, 731, 2.0) BODIES = {"mercury": 1, "venus": 2, "mars": 4, "emb": 3}
eph = {"DE440": CalcephBin.open(os.path.join(KDIR, "de440s.bsp")),
"INPOP21a": CalcephBin.open(os.path.join(KDIR, "inpop21a.dat")),
"EPM2021": CalcephBin.open(os.path.join(KDIR, "epm2021.bsp"))}
def pos(e, tgt, ctr, jd):
return np.array(eph[e].compute_unit(jd, 0.0, tgt, ctr, UNIT)[:3]) * M
def ncx(r):
x, y, z = r
return np.array([[0, z, -y], [-z, 0, x], [y, -x, 0]])
def fit_rot(ra, rb):
A = np.vstack([np.hstack([np.eye(3), ncx(r)]) for r in ra])
y = (rb - ra).reshape(-1)
b, *_ = np.linalg.lstsq(A, y, rcond=None)
res = y - A @ b
return b[3:6], float(np.sqrt((res ** 2).mean() * 3))
CSV_DP = 6
moon = {p: np.round(np.array([pos(p, 301, 399, JD0 + d) for d in DAYS]), CSV_DP) for p in eph}
planet = {p: {bn: np.round(np.array([pos(p, bid, 0, JD0 + d) for d in DAYS]), CSV_DP)
for bn, bid in BODIES.items()} for p in eph}
os.makedirs(OUT, exist_ok=True)
with open(os.path.join(OUT, "moon_geo.csv"), "w", newline="") as f:
w = csv.writer(f); w.writerow(["day", "provider", "x_m", "y_m", "z_m"])
for p in eph:
for i, d in enumerate(DAYS):
w.writerow([f"{d:.1f}", p, f"{moon[p][i][0]:.6f}", f"{moon[p][i][1]:.6f}", f"{moon[p][i][2]:.6f}"])
with open(os.path.join(OUT, "planet_ssb.csv"), "w", newline="") as f:
w = csv.writer(f); w.writerow(["day", "provider", "body", "x_m", "y_m", "z_m"])
for p in eph:
for bn in BODIES:
for i, d in enumerate(DAYS):
r = planet[p][bn][i]
w.writerow([f"{d:.1f}", p, bn, f"{r[0]:.6f}", f"{r[1]:.6f}", f"{r[2]:.6f}"])
ref = {"window": "2024-01-01..2025-12-31 TDB", "epochs": len(DAYS), "cadence_days": 2,
"epoch_jd_tdb": JD0, "lunar_dist_m": LUNAR_DIST, "providers": list(eph), "pairs": {}}
for a, b in [("DE440", "INPOP21a"), ("DE440", "EPM2021"), ("INPOP21a", "EPM2021")]:
ra, rb = moon[a], moon[b]
raw = float(np.sqrt((np.linalg.norm(rb - ra, axis=1) ** 2).mean()))
th_moon, rot_res = fit_rot(ra, rb)
thetas = [fit_rot(planet[a][bn], planet[b][bn])[0] for bn in BODIES]
th_tie = np.median(np.array(thetas), axis=0)
th_exc = th_moon - th_tie
ref["pairs"][f"{a}-{b}"] = {
"raw_rms_m": raw, "rot_residual_m": rot_res,
"theta_moon_nrad": float(np.linalg.norm(th_moon) * 1e9),
"theta_frametie_nrad": float(np.linalg.norm(th_tie) * 1e9),
"theta_excess_nrad": float(np.linalg.norm(th_exc) * 1e9),
"reducible_m": float(np.linalg.norm(th_tie) * LUNAR_DIST),
"irreducible_m": float(np.linalg.norm(th_exc) * LUNAR_DIST),
"theta_moon_vec_nrad": [float(x * 1e9) for x in th_moon],
"theta_frametie_vec_nrad": [float(x * 1e9) for x in th_tie]}
with open(os.path.join(OUT, "reference.json"), "w") as f:
json.dump(ref, f, indent=2)
print(json.dumps(ref["pairs"], indent=2))
print("\nwrote:", os.path.join(OUT, "moon_geo.csv"), "/ planet_ssb.csv / reference.json")