feat(domy): topocentric zgodny co do zera — wybór gałęzi liczony, nie zgadywany
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) Successful in 18s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 9s
build-render / build (push) Successful in 5m34s
build / build (push) Successful in 39s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m46s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m31s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m27s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 19s
Testy / Kontrola składni wszystkich warstw (push) Successful in 11s
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) Successful in 18s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 9s
build-render / build (push) Successful in 5m34s
build / build (push) Successful in 39s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m46s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m31s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m27s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 19s
Testy / Kontrola składni wszystkich warstw (push) Successful in 11s
Topocentric był wycofany z powodu rozjazdu przy |φ| ≈ 89,9°. Ta diagnoza była BŁĘDNA: opierała się wyłącznie na zestawie brzegowym, który próbkuje tylko wybrane szerokości. Przemiał losowy pokazał prawdziwą skalę — błędy od ~70° w górę, do 50% przypadków blisko biegunów, 6,5% całości. Nie dwa przypadki brzegowe. Przyczyną nie była jednak konstrukcja, tylko wybór gałęzi: dwa koła wielkie przecinają się w dwóch punktach antypodycznych. Kolejno zawiodły reguły „po której stronie MC", „w łuku kwadrantu", „wschodnia połowa horyzontu" i śledzenie ciągłości krokami (to ostatnie maskuje własną patologię — po korekcie do bliższej gałęzi zmierzony skok ZAWSZE wychodzi ≤ 90°, więc detektor nigdy się nie zapala). Wszystkie te reguły rozstrzygają lokalnie, a przy dużych szerokościach kolejność domów potrafi się odwrócić: przy φ = −79,55° MC wypada na 306,8°, a dom 11 na 291,4°. To jest poprawne — wyrocznia zwraca to samo. Rozwiązanie: nie wybierać w ogóle. Iloczyn wektorowy zenitu z biegunem ekliptyki jest ciągłą funkcją parametru rodziny i sam niesie właściwy zwrot; dwuznaczność wprowadza dopiero atan2. Zostajemy w wektorach, znak ustalamy raz — kotwicząc rodzinę na MC górującym. Wynik: 0,000000000° na 240 000 porównań, cała dziedzina, bez iteracji i bez zawężania szerokości. Żadna granica dziedziny nie jest tu potrzebna, więc żadnej nie udajemy — test pilnuje, że topocentric liczy się wszędzie i nie fallbackuje. Morał do frameworka: zestaw brzegowy mówi, CZY system się psuje; dopiero przemiał losowy mówi JAK BARDZO. Wniosek o skali wyciągnięty z samych brzegów był tu zaniżony o trzy rzędy wielkości. Odnotowane w tests/oracle/README.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit was merged in pull request #63.
This commit is contained in:
@@ -25,14 +25,11 @@ ALCABITUS = "alcabitus"
|
||||
TOPOCENTRIC = "topocentric"
|
||||
PLACIDUS = "placidus"
|
||||
KOCH = "koch"
|
||||
# Systemy WYPUSZCZONE — każdy zweryfikowany wobec Swiss Ephemeris co do zera
|
||||
# (tests/oracle). TOPOCENTRIC celowo POZA listą: implementacja jest zgodna
|
||||
# z wyrocznią wszędzie poza otoczeniem bieguna (|φ| ≈ 89,9° przy RAMC 90°/270°),
|
||||
# gdzie kolejność domów się odwraca i konstrukcja traci sens — jego „biegun"
|
||||
# atan(tan(89,9°)/3) to już 89,7°. Nie zawężamy dziedziny wyłącznie po to, by
|
||||
# testy przeszły; system dołączy, gdy będzie poprawny na całej dziedzinie.
|
||||
SYSTEMS = (WHOLE_SIGN, EQUAL, PORPHYRY, VEHLOW, MORINUS,
|
||||
REGIOMONTANUS, CAMPANUS, ALCABITUS, PLACIDUS, KOCH)
|
||||
# Systemy WYPUSZCZONE — każdy zweryfikowany wobec Swiss Ephemeris
|
||||
# (tests/oracle). Placidus i Koch jako jedyne mają granicę dziedziny:
|
||||
# powyżej koła podbiegunowego nie istnieją i podlegają jawnemu fallbackowi.
|
||||
SYSTEMS = (WHOLE_SIGN, EQUAL, PORPHYRY, VEHLOW, MORINUS, REGIOMONTANUS,
|
||||
CAMPANUS, ALCABITUS, TOPOCENTRIC, PLACIDUS, KOCH)
|
||||
|
||||
|
||||
def mean_obliquity(tt_jd: float) -> float:
|
||||
@@ -289,44 +286,54 @@ def _cusps_alcabitus(ramc: float, eps: float, asc: float) -> list[float]:
|
||||
|
||||
# Ułamek szerokości geograficznej użyty jako „biegun" koła domu (Polich–Page).
|
||||
# Domy na południku (10 i 4) mają biegun 0 — ich koło to sam południk.
|
||||
_TOPO_POLE_FRACTION = (1.0, 2 / 3, 1 / 3, 0.0, 1 / 3, 2 / 3,
|
||||
1.0, 2 / 3, 1 / 3, 0.0, 1 / 3, 2 / 3)
|
||||
# Polich–Page: dom pośredni to Ascendent policzony pod własnym „biegunem"
|
||||
# tan(P) = tan(φ)·k/3, dla RAMC przesuniętego o pozycję domu. Rodzina jest CIĄGŁA:
|
||||
# przy k=0 (biegun 0, przesunięcie −90°) daje MC, przy k=3 (biegun φ, przesunięcie 0)
|
||||
# Ascendent, a domy 11 i 12 leżą po drodze.
|
||||
#
|
||||
# Cała trudność tego systemu siedziała w wyborze gałęzi — dwa koła wielkie
|
||||
# przecinają się w dwóch punktach antypodycznych. Heurystyki („po której stronie
|
||||
# MC", „w łuku kwadrantu", „wschodnia połowa horyzontu", śledzenie ciągłości
|
||||
# krokami) myliły się na 6–11% przypadków powyżej ~70°, bo każda z nich rozstrzyga
|
||||
# LOKALNIE, a przy dużych szerokościach kolejność domów potrafi się odwrócić.
|
||||
#
|
||||
# Rozwiązanie: nie wybierać w ogóle. Iloczyn wektorowy zenitu z biegunem ekliptyki
|
||||
# jest ciągłą funkcją parametru rodziny i sam niesie właściwy zwrot — dwuznaczność
|
||||
# wprowadza dopiero atan2. Zostajemy więc w wektorach, a znak ustalamy RAZ, kotwicząc
|
||||
# rodzinę na MC górującym. Stąd zgodność co do zera na całej dziedzinie, bez iteracji
|
||||
# i bez zawężania szerokości.
|
||||
|
||||
|
||||
def _asc_under_pole_raw(ramc_deg: float, eps_deg: float, pole_deg: float) -> float:
|
||||
"""Surowy wzór na ascendent pod zadanym „biegunem", BEZ korekty gałęzi.
|
||||
|
||||
Korektę stosuje wywołujący — względem PRAWDZIWEGO MC horoskopu. `compute_asc`
|
||||
poprawia gałąź względem MC dla PRZESUNIĘTEGO RAMC, co dla cuspu domu jest złym
|
||||
punktem odniesienia i w okolicach biegunów dawało obrót o 180°."""
|
||||
r, e, phi = math.radians(ramc_deg), math.radians(eps_deg), math.radians(pole_deg)
|
||||
return norm360(math.degrees(math.atan2(
|
||||
math.cos(r), -(math.sin(r) * math.cos(e) + math.tan(phi) * math.sin(e)))))
|
||||
# Dom → (przesunięcie RAMC [°], ułamek bieguna k/3).
|
||||
_TOPO_STEP = {10: (-60.0, 1 / 3), 11: (-30.0, 2 / 3), # domy 11, 12
|
||||
1: (30.0, 2 / 3), 2: (60.0, 1 / 3)} # domy 2, 3
|
||||
|
||||
|
||||
def _cusps_topocentric(ramc: float, eps: float, lat: float,
|
||||
asc: float, mc: float) -> list[float]:
|
||||
"""Polich–Page: dom pośredni to ASCENDENT policzony pod własnym „biegunem"
|
||||
tan(P) = tan(φ)·k/3, dla RAMC przesuniętego o pozycję domu.
|
||||
"""Ascendenty pod biegunami tan(P) = tan(φ)·k/3, liczone wektorowo.
|
||||
|
||||
Domy 5, 6, 8, 9 bierzemy jako OPOZYCJE domów 11, 12, 2, 3 — to nie skrót,
|
||||
lecz własność konstrukcji: przeciwległe domy leżą na tym samym kole wielkim.
|
||||
|
||||
Kusi, by liczyć to jak Regiomontanusa z podmienioną szerokością — daje wynik
|
||||
bliski, ale nie równy (kilka sekund łuku); wyrocznia rozstrzygnęła na rzecz
|
||||
konstrukcji „ascendent pod biegunem".
|
||||
|
||||
Liczymy tylko domy 11, 12, 2, 3, a 5, 6, 8, 9 bierzemy jako ich OPOZYCJE —
|
||||
to nie skrót, lecz własność tych systemów: przeciwległe domy leżą na tym samym
|
||||
kole wielkim, więc ich cuspy są dokładnie antypodyczne."""
|
||||
tan_lat = math.tan(math.radians(lat))
|
||||
# Gałąź liczymy względem MC GÓRUJĄCEGO, nie matematycznego: gdy za kołem
|
||||
# podbiegunowym te dwa się rozjeżdżają, cała czwórka domów pośrednich musi
|
||||
# obrócić się razem z dziesiątym domem.
|
||||
konstrukcji „ascendent pod biegunem"."""
|
||||
er, tan_lat = math.radians(eps), math.tan(math.radians(lat))
|
||||
epole = _ecliptic_pole(er)
|
||||
# Gdy MC górujące rozjeżdża się z matematycznym (za kołem podbiegunowym),
|
||||
# cała rodzina obraca się razem z dziesiątym domem — stąd zwrot iloczynu.
|
||||
culminating = _culminating_mc(mc, eps, lat)
|
||||
sign = 1.0 if abs(((culminating - mc + 180.0) % 360.0) - 180.0) > 90.0 else -1.0
|
||||
|
||||
out = [0.0] * 12
|
||||
for i in (10, 11, 1, 2): # domy 11, 12, 2, 3
|
||||
pole = math.degrees(math.atan(tan_lat * _TOPO_POLE_FRACTION[i]))
|
||||
lon = _asc_under_pole_raw(ramc + 30.0 * i, eps, pole)
|
||||
if ((lon - culminating) % 360.0 < 180.0) != _INTERMEDIATE[i]:
|
||||
lon = norm360(lon + 180.0)
|
||||
for i, (offset, fraction) in _TOPO_STEP.items():
|
||||
th = math.radians(ramc + offset)
|
||||
pole = math.atan(tan_lat * fraction)
|
||||
zenith = (math.cos(pole) * math.cos(th),
|
||||
math.cos(pole) * math.sin(th),
|
||||
math.sin(pole))
|
||||
v = _cross(zenith, epole)
|
||||
lon = _equatorial_to_lon(tuple(sign * x for x in v), er)
|
||||
out[i] = lon
|
||||
out[(i + 6) % 12] = norm360(lon + 180.0)
|
||||
out[0], out[3] = asc, norm360(culminating + 180.0)
|
||||
|
||||
Reference in New Issue
Block a user