Files
Jyotisha/tests/test_angle_timing_research.py
T

102 lines
4.2 KiB
Python

"""Angle-timing research helpers (BUG-1141). Offline; public v5 cases only."""
from __future__ import annotations
from datetime import date
from random import Random
import numpy as np
import pytest
from scripts.research import angle_timing_lib as at
def test_angle_diff_wraps_and_aspect_sep_takes_nearest_aspect():
assert at.angle_diff(359.0, 1.0) == pytest.approx(2.0)
assert at.angle_diff(10.0, 190.0) == pytest.approx(180.0)
seps = at.aspect_sep(np.array([0.0, 91.0, 178.5, 45.0]), np.array([0.0, 0.0, 0.0, 0.0]))
assert list(np.round(seps, 3)) == [0.0, 1.0, 1.5, 45.0]
@pytest.mark.parametrize("raw, precision, start, end", [
({"date": "2001-03-04", "precision": "day"}, "day", date(2001, 3, 4), date(2001, 3, 4)),
({"date": "2001-02", "precision": "month"}, "month", date(2001, 2, 1), date(2001, 2, 28)),
({"date": "2001", "precision": "year"}, "year", date(2001, 1, 1), date(2001, 12, 31)),
])
def test_parse_event_spans(raw, precision, start, end):
event = at.parse_event({**raw, "id": "x", "domain": "career"})
assert event is not None and (event.precision, event.start, event.end) == (precision, start, end)
def test_parse_event_rejects_future_and_malformed():
assert at.parse_event({"date": "2030", "precision": "year"}) is None
assert at.parse_event({"date": "2001-13", "precision": "month"}) is None
assert at.parse_event({"date": "", "precision": "day"}) is None
def test_sample_dates_by_technique_and_precision():
month = at.parse_event({"date": "2001-02", "precision": "month"})
year = at.parse_event({"date": "2001", "precision": "year"})
assert len(at.sample_dates(month, "TR")) == 28
assert len(at.sample_dates(month, "SP")) == 1
assert at.sample_dates(year, "TR") == []
assert len(at.sample_dates(year, "SA")) == 5
def test_truth_rank_semantics():
scores = np.zeros(121)
scores[at.RADIUS_MAX] = 3
assert at.truth_rank(scores, 10) == 0.0
flat = np.ones(121)
assert at.truth_rank(flat, 10) == pytest.approx(0.5)
worst = np.ones(121); worst[at.RADIUS_MAX] = 0
assert at.truth_rank(worst, 10) == 1.0
def test_shuffle_keeps_precision_count_and_is_seeded():
case = at.load_cases()[0]
a = at.shuffled_events(case, Random("seed-1"))
b = at.shuffled_events(case, Random("seed-1"))
assert a == b
assert [e["precision"] for e in a] == [e["precision"] for e in case["events"]]
assert [len(e["date"]) for e in a] == [len(str(e["date"])[:{"day": 10, "month": 7}.get(e["precision"], 4)]) for e in case["events"]]
birth_year = int(case["birth"]["date"][:4])
assert all(int(e["date"][:4]) > birth_year for e in a)
def test_natal_angles_move_about_a_quarter_degree_per_minute():
chart = at.CaseChart(at.load_cases()[0])
step_asc = at.angle_diff(chart.asc[1:], chart.asc[:-1])
step_mc = at.angle_diff(chart.mc[1:], chart.mc[:-1])
assert 0.05 < float(np.median(step_asc)) < 1.0
assert 0.2 < float(np.median(step_mc)) < 0.3
# the truth minute is the window centre
assert chart.offsets[at.RADIUS_MAX] == 0
def test_separation_matrices_have_one_row_per_usable_event():
chart = at.CaseChart(at.load_cases()[0])
events = chart.events()
tr = chart.separations("TR", events)
sp = chart.separations("SP", events)
assert tr.shape[0] == sum(1 for e in events if e.precision != "year")
assert sp.shape == (len(events), len(chart.offsets))
assert np.isfinite(sp).all() and (sp >= 0).all() and (sp <= 45).all()
def test_a1_runner_is_deterministic_and_reports_every_preregistered_test():
from scripts.research import angle_timing_research as runner
cases = at.load_cases()[:2]
first = runner.run_a1(cases, 3, lambda _m: None)
second = runner.run_a1(cases, 3, lambda _m: None)
assert at.stable_json(first) == at.stable_json(second)
assert first["tests"] == 9
assert first["bonferroni_percentile"] == pytest.approx(100 * (1 - 0.05 / 9), abs=1e-3)
for tech in at.TECHNIQUES:
for orb in at.ORBS:
for radius in runner.RADII:
cell = first["table"][tech][str(orb)][str(radius)]
assert 0.0 <= cell["real_mean_rank"] <= 1.0
assert 0.0 <= cell["percentile_beaten"] <= 100.0
assert len(first["shuffled_means"][tech][str(orb)][str(radius)]) == 3