"""Horospire's engine values, rebuilt from the published errors. Horospire ephemeris accuracy study, version 1.0. MIT licence (LICENSE-CODE.txt). Usage: python house_from_errors.py WORK_DIR DAILY_ERRORS_CSV_GZ Version 1.0 does not publish Horospire's engine, so house_engine.php cannot be run outside Horospire. This writes WORK_DIR/house.json from the reference (reference.py) and the published errors instead: engine = reference + error. Run summarise.py after it and every figure in summary.csv comes back, which checks the published errors against the published summary and the reference you computed yourself. The engine returns each longitude rounded to 0.0001 degree, and many land exactly on a boundary (30.0000 is Taurus). The rebuilt value is rounded the same way, which recovers the engine's own figure exactly: the reference keeps seven decimals of a degree and the errors three decimals of an arcsecond, so the rebuild is never more than 0.0000002 degree away before rounding. """ import csv import gzip import io import json import os import sys def main(): work, errors_path = sys.argv[1:3] reference = json.load(open(os.path.join(work, "reference.json"))) moments = json.load(open(os.path.join(work, "moments.json"))) with gzip.open(errors_path, "rb") as handle: rows = list(csv.reader(io.TextIOWrapper(handle, encoding="utf-8"))) head, rows = rows[0], rows[1:] if [r[0] for r in rows] != moments: sys.exit("The errors file and moments.json name different moments.") columns = [head.index(f"{b}_error_arcsec") for b in reference["bodies"]] def engine(ref, error): value = round((ref + error / 3600.0) % 360.0, 4) return 0.0 if value >= 360.0 else value lon = [[engine(ref, float(row[c])) for ref, c in zip(reference["lon"][i], columns)] for i, row in enumerate(rows)] json.dump({"engine": "Horospire EphemerisEngine (from the published errors)", "lon": lon}, open(os.path.join(work, "house.json"), "w")) print(f"{len(lon)} moments") if __name__ == "__main__": main()