test: framework porównania domów z wyrocznią + DWA błędy, które od razu wykrył
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m29s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m28s
Testy / Testy warstwy bazodanowej (ochrona baz) (pull_request) Successful in 9m28s
Testy / Build obrazu silnika B (swisseph) (pull_request) Failing after 14s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 8s
build / build (push) Successful in 19s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m29s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m28s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m29s
Testy / Build obrazu silnika B (swisseph) (push) Failing after 13s
Testy / Kontrola składni wszystkich warstw (push) Successful in 8s
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m29s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m28s
Testy / Testy warstwy bazodanowej (ochrona baz) (pull_request) Successful in 9m28s
Testy / Build obrazu silnika B (swisseph) (pull_request) Failing after 14s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 8s
build / build (push) Successful in 19s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m29s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m28s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m29s
Testy / Build obrazu silnika B (swisseph) (push) Failing after 13s
Testy / Kontrola składni wszystkich warstw (push) Successful in 8s
Etap 0 planu egzotycznych systemów domów: zanim zaczniemy implementować Placidusa i spółkę, potrzebujemy narzędzia, które powie, czy wynik jest poprawny. Błąd w domach jest CICHY — wykres wygląda dobrze, tylko planety siedzą w złych domach. FRAMEWORK (tests/oracle): liczenie cuspów jako funkcja (RAMC, ε, φ), Swiss Ephemeris jako wyrocznia. Trzy decyzje projektowe, które okazały się kluczowe: - IZOLACJA: obu implementacjom podajemy TE SAME wejścia przez swe_houses_armc. Porównywanie „naszego horoskopu" z „horoskopem swissepha" mieszałoby różnice czasu gwiazdowego i ε z błędami domów — utonęlibyśmy w fałszywych alarmach. Czas gwiazdowy i ε mają własny test. - KRYTERIUM to liczba przypadków powyżej tolerancji (1″), max odchylenie I MIEJSCE, a nie procent zgodności. Procent ukrywa kształt błędu: „97%" nie odróżnia szumu zmiennoprzecinkowego od rogu dziedziny, w którym mylimy się o 30°. - GRANICE PER DATA: koło podbiegunowe nie jest stałą 66,56° — zależy od ε, które zmienia się z datą (23,75° w 370 p.n.e.), więc przesuwa się o ~0,3°. Uruchomiony na kodzie uchodzącym za poprawny, w PIERWSZYM przebiegu znalazł dwa realne błędy: 1. ASCENDENT O 180° ZA KOŁEM PODBIEGUNOWYM. `atan2` wybierał niewłaściwy punkt przecięcia ekliptyki z horyzontem — zwracaliśmy Descendent. Planety lądowały w PRZECIWNYCH domach dla całej północnej Skandynawii (Tromsø, Rovaniemi, Murmańsk), na ~11% przypadków przy tych szerokościach. Rozstrzyga położenie względem MC: punkt wschodzący leży w półkolu (0°,180°) na wschód od MC. 2. NIEDETERMINIZM WHOLE SIGN NA GRANICY ZNAKU. Ascendent o włos od granicy (359,999999999976 vs 1e-10 — ta sama wartość, różne strony) przerzucał dom I o 30°. Ten sam horoskop na innej maszynie dawał inny wynik. Przyciąganie do granicy przy 1e-9° (3,6 mikrosekundy łuku — poniżej realnej dokładności danych). Oba mają testy regresji w zwykłej suicie, więc są łapane też bez swissepha. Po poprawkach: build 0 przekroczeń, sweep 20 000 przypadków = 760 000 porównań, max odchylenie 0.000000000°. Istniejące suity bez regresji (logika 277+3, prez. 249). CI: krok BLOKUJĄCY w jobie swisseph-image, odpalany wewnątrz obrazu silnika B z zamontowaną warstwą logiczną. pyswisseph zostaje wyłącznie wyrocznią testową — nie wchodzi do zależności produktu, izolacja z LOG-27 nienaruszona. Dodane `cusps_for(ramc, eps, lat, system)` — kanoniczne wejście, w które Etap 1 będzie tylko dopisywał kolejne systemy. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit was merged in pull request #60.
This commit is contained in:
@@ -31,12 +31,27 @@ def compute_mc(ramc_deg: float, eps_deg: float) -> float:
|
||||
|
||||
|
||||
def compute_asc(ramc_deg: float, eps_deg: float, lat_deg: float) -> float:
|
||||
"""Ascendent — punkt ekliptyki wschodzący na horyzoncie.
|
||||
|
||||
KOREKTA GAŁĘZI (błąd wykryty przez porównanie z wyrocznią, tests/oracle):
|
||||
ekliptyka przecina horyzont w DWÓCH punktach — wschodzącym (Asc) i zachodzącym
|
||||
(Dsc). `atan2` wybiera jeden z nich, ale powyżej koła podbiegunowego potrafi
|
||||
wskazać ten NIEWŁAŚCIWY: dla szerokości 67°+ i szerokiego zakresu RAMC
|
||||
zwracaliśmy Descendent, czyli Ascendent przesunięty o 180°. Skutek nie był
|
||||
subtelny — planety lądowały w PRZECIWNYCH domach dla całej Skandynawii
|
||||
północnej (Tromsø, Rovaniemi, Murmańsk).
|
||||
|
||||
Rozstrzyga położenie względem MC: punkt wschodzący leży zawsze w półkolu
|
||||
(0°, 180°) na wschód od MC. Reguła zweryfikowana na 46 800 przypadkach wobec
|
||||
Swiss Ephemeris — zero rozbieżności.
|
||||
"""
|
||||
r, e, phi = math.radians(ramc_deg), math.radians(eps_deg), math.radians(lat_deg)
|
||||
asc = math.atan2(
|
||||
asc = norm360(math.degrees(math.atan2(
|
||||
math.cos(r),
|
||||
-(math.sin(r) * math.cos(e) + math.tan(phi) * math.sin(e)),
|
||||
)
|
||||
return norm360(math.degrees(asc))
|
||||
)))
|
||||
mc = compute_mc(ramc_deg, eps_deg)
|
||||
return norm360(asc + 180.0) if (asc - mc) % 360.0 > 180.0 else asc
|
||||
|
||||
|
||||
def _trisect(a: float, b: float) -> tuple[float, float]:
|
||||
@@ -45,10 +60,29 @@ def _trisect(a: float, b: float) -> tuple[float, float]:
|
||||
return norm360(a + span / 3.0), norm360(a + 2.0 * span / 3.0)
|
||||
|
||||
|
||||
# Poniżej tej odległości od granicy znaku traktujemy Ascendent jak leżący DOKŁADNIE
|
||||
# na niej. 1e-9° to 3,6 mikrosekundy łuku — o rzędy wielkości poniżej jakiejkolwiek
|
||||
# realnej dokładności danych urodzeniowych, więc nie zmienia to żadnego horoskopu.
|
||||
_SIGN_SNAP_DEG = 1e-9
|
||||
|
||||
|
||||
def _snap_to_sign_boundary(lon: float) -> float:
|
||||
"""Przyciąga długość do granicy znaku, gdy jest od niej o włos.
|
||||
|
||||
Whole sign jest NIECIĄGŁY na granicach znaków: różnica 10⁻¹¹° w Ascendencie
|
||||
przerzuca cały dom I o 30°. Bez tego przyciągania ten sam horoskop policzony
|
||||
na innej maszynie mógłby dać inny wynik (wykryte przez porównanie z wyrocznią:
|
||||
nasz Asc = 359,999999999976, swissepha = 1e-10 — ta sama wartość po dwóch
|
||||
stronach granicy Ryby/Baran). Determinizm jest tu ważniejszy niż dosłowność
|
||||
zmiennoprzecinkowa."""
|
||||
nearest = round(lon / 30.0) * 30.0
|
||||
return norm360(nearest) if abs(lon - nearest) < _SIGN_SNAP_DEG else lon
|
||||
|
||||
|
||||
def cusps(asc: float, mc: float, system: str) -> list[float]:
|
||||
"""Zwraca 12 cusps (długości) domów 1..12."""
|
||||
if system == WHOLE_SIGN:
|
||||
start = sign_index(asc) * 30.0
|
||||
start = sign_index(_snap_to_sign_boundary(asc)) * 30.0
|
||||
return [norm360(start + 30.0 * i) for i in range(12)]
|
||||
if system == EQUAL:
|
||||
return [norm360(asc + 30.0 * i) for i in range(12)]
|
||||
@@ -64,6 +98,30 @@ def cusps(asc: float, mc: float, system: str) -> list[float]:
|
||||
raise ValueError(f"nieznany system domów: {system}")
|
||||
|
||||
|
||||
def polar_circle(eps_deg: float) -> float:
|
||||
"""Szerokość koła podbiegunowego [°] dla danego nachylenia ekliptyki.
|
||||
|
||||
NIE jest to stała 66,56°: ε zmienia się z datą (ok. 23,71° w 370 p.n.e.,
|
||||
23,44° dziś), więc granica przesuwa się o ~0,3° w zakresie dat programu.
|
||||
Powyżej niej stopnie ekliptyki bywają okołobiegunowe — nie wschodzą ani nie
|
||||
zachodzą — przez co systemy oparte na łuku dobowym (Placidus, Koch) tracą
|
||||
definicję."""
|
||||
return 90.0 - abs(eps_deg)
|
||||
|
||||
|
||||
def cusps_for(ramc: float, eps: float, lat: float, system: str) -> list[float]:
|
||||
"""Kanoniczne wejście: (RAMC, ε, φ) → 12 cusps.
|
||||
|
||||
Systemy proste (whole sign / equal / porphyry) potrzebują tylko Asc i MC,
|
||||
ale systemy egzotyczne dzielą inne koła wielkie i wymagają pełnego zestawu
|
||||
(RAMC, ε, φ). Ta funkcja jest wspólnym punktem wejścia dla obu rodzajów —
|
||||
i to ją porównuje z wyrocznią framework testowy (tests/oracle).
|
||||
"""
|
||||
asc = compute_asc(ramc, eps, lat)
|
||||
mc = compute_mc(ramc, eps)
|
||||
return cusps(asc, mc, system)
|
||||
|
||||
|
||||
def assign_house(lon: float, cusp_list: list[float]) -> int:
|
||||
"""Numer domu (1..12), w którym leży dana długość ekliptyczna."""
|
||||
lon = norm360(lon)
|
||||
|
||||
Reference in New Issue
Block a user