Files
Jyotisha/tests/test_varga_bphs.py
T

217 lines
8.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Regression tests for BPHS divisional chart mappings."""
from __future__ import annotations
from hypothesis import given
from hypothesis import strategies as st
from varga import (
SIGNS,
calc_22nd_drekkana,
calc_64th_navamsa,
calc_all_vargas,
calc_bhrigu_bindu,
calc_sarpa_drekkana,
calc_varga,
varga_map,
)
def navamsa_ref(lon: float) -> int:
sign_index = int((lon % 360) / 30) % 12
degree_in_sign = (lon % 360) - sign_index * 30
part_index = int(degree_in_sign / (30 / 9))
if sign_index % 3 == 0:
start = sign_index
elif sign_index % 3 == 1:
start = (sign_index + 8) % 12
else:
start = (sign_index + 4) % 12
return (start + part_index) % 12
def dasamsa_ref(lon: float) -> int:
sign_index = int((lon % 360) / 30) % 12
degree_in_sign = (lon % 360) - sign_index * 30
part_index = int(degree_in_sign / 3)
start = sign_index if sign_index % 2 == 0 else (sign_index + 8) % 12
return (start + part_index) % 12
def drekkana_ref(lon: float) -> int:
sign_index = int((lon % 360) / 30) % 12
degree_in_sign = (lon % 360) - sign_index * 30
part_index = int(degree_in_sign / 10)
return (sign_index + part_index * 4) % 12
def chaturthamsa_ref(lon: float) -> int:
sign_index = int((lon % 360) / 30) % 12
degree_in_sign = (lon % 360) - sign_index * 30
part_index = int(degree_in_sign / (30 / 4))
return (sign_index + part_index * 3) % 12
@given(st.floats(min_value=0, max_value=359.999999, allow_nan=False, allow_infinity=False))
def test_navamsa_matches_bphs_reference(lon: float) -> None:
result = calc_varga(lon, 9)
assert result["sign_idx"] == navamsa_ref(lon)
assert result["sign"] == SIGNS[navamsa_ref(lon)]
assert 0 <= result["degree_in_sign"] < 30
@given(st.floats(min_value=0, max_value=359.999999, allow_nan=False, allow_infinity=False))
def test_dasamsa_matches_bphs_reference(lon: float) -> None:
result = calc_varga(lon, 10)
assert result["sign_idx"] == dasamsa_ref(lon)
assert result["sign"] == SIGNS[dasamsa_ref(lon)]
assert 0 <= result["degree_in_sign"] < 30
@given(st.floats(min_value=0, max_value=359.999999, allow_nan=False, allow_infinity=False))
def test_drekkana_uses_same_plus_four_plus_eight(lon: float) -> None:
result = calc_varga(lon, 3)
assert result["sign_idx"] == drekkana_ref(lon)
@given(st.floats(min_value=0, max_value=359.999999, allow_nan=False, allow_infinity=False))
def test_chaturthamsa_matches_pyjhora_parashara_reference(lon: float) -> None:
result = calc_varga(lon, 4)
assert result["sign_idx"] == chaturthamsa_ref(lon)
assert result["sign"] == SIGNS[chaturthamsa_ref(lon)]
assert 0 <= result["degree_in_sign"] < 30
def test_chaturthamsa_reference_boundary_examples() -> None:
assert [varga_map(0, part, 4) for part in range(4)] == [0, 3, 6, 9]
assert [varga_map(8, part, 4) for part in range(4)] == [8, 11, 2, 5]
assert [varga_map(11, part, 4) for part in range(4)] == [11, 2, 5, 8]
def test_varga_map_boundary_examples() -> None:
assert varga_map(0, 0, 9) == 0 # Aries Navamsa starts Aries
assert varga_map(1, 0, 9) == 9 # Taurus Navamsa starts Capricorn (9th from sign)
assert varga_map(2, 0, 9) == 6 # Gemini Navamsa starts Libra (5th from sign)
assert varga_map(1, 0, 10) == 9 # Taurus Dasamsa starts Capricorn
assert varga_map(0, 2, 3) == 8 # Aries third Drekkana = Sagittarius
def test_rudramsa_d11_is_supported_by_native_varga_map() -> None:
assert varga_map(0, 0, 11) == 0
assert varga_map(0, 10, 11) == 10
# 原值: varga_map(1, 0, 11) == 9;varga_map(1, 10, 11) == 7;calc_varga(35) == Aquarius
# 新值: 11、9、Aries
# 原因: BUG-1260,D11 改按 PVR 6.2.11 从白羊逆数起点,再顺数各份
assert varga_map(1, 0, 11) == 11
assert varga_map(1, 10, 11) == 9
result = calc_varga(35.0, 11)
assert result["sign"] == "Aries"
assert 0 <= result["degree_in_sign"] < 30
def test_native_varga_map_supports_declared_high_value_vargas() -> None:
for div in (2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 24, 60, 81, 108, 144):
result = calc_varga(35.0, div)
assert result["sign"] in SIGNS
assert 0 <= result["degree_in_sign"] < 30
def test_default_varga_batch_includes_declared_vargas() -> None:
charts = calc_all_vargas({"Moon": 35.0}, 10.0)
for key in ("D5_Panchamsa", "D6_Shashthamsa", "D8_Ashtamsa", "D11_Rudramsa", "D81_Navamsa-Navamsa", "D108_Dwadasamsa-Navamsa", "D144_Dwadasamsa-Dwadasamsa"):
assert key in charts
# 原值: Aquarius / 新值: Aries / 原因: BUG-1260,35° 的 D11 按 PVR 6.2.11 落到白羊
assert charts["D11_Rudramsa"]["Moon"]["sign"] == "Aries"
assert "6.2.5" in charts["D5_Panchamsa"]["_meta"]["mapping_source"]
assert "6.2.11" in charts["D11_Rudramsa"]["_meta"]["mapping_source"]
def test_pvr_d5_d6_d8_d11_worked_examples() -> None:
"""Hand counts from PVR Integrated Approach 6.2.5 / 6.2.6 / 6.2.8 / 6.2.11."""
# 6.2.5: odd parts Ar, Aq, Sg, Ge, Li; even Ta, Vi, Pi, Cp, Sc.
# 11° Gemini is the 2nd part of an odd sign.
assert calc_varga(60 + 11, 5)["sign"] == "Aquarius"
# 19° Taurus is the 4th part of an even sign.
assert calc_varga(30 + 19, 5)["sign"] == "Capricorn"
# 6.2.6: 11° Gemini -> Gemini; 19° Scorpio -> Capricorn.
assert calc_varga(60 + 11, 6)["sign"] == "Gemini"
assert calc_varga(210 + 19, 6)["sign"] == "Capricorn"
# 6.2.8 counting: 10° Gemini is the 3rd part of a dual sign, 3rd from Leo is Libra.
# 19° Scorpio is the 6th part of a fixed sign, 6th from Sagittarius is Taurus.
assert calc_varga(60 + 10, 8)["sign"] == "Libra"
assert calc_varga(210 + 19, 8)["sign"] == "Taurus"
# 6.2.11: 11° Gemini -> Gemini; 19° Scorpio -> Pisces.
assert calc_varga(60 + 11, 11)["sign"] == "Gemini"
assert calc_varga(210 + 19, 11)["sign"] == "Pisces"
def test_navamsa_matches_user_jhora_pdf_reference_chart() -> None:
"""Regression from private_chart_reference.pdf: JHora-style D9 table for public sample birth datetime San Francisco."""
expected = {
132.355025: "Cancer", # Ascendant 12 Leo 21'18.09"
3.5226611111111112: "Taurus",
311.78995555555554: "Capricorn",
91.33091388888889: "Cancer",
338.5488: "Virgo",
163.83150833333335: "Taurus",
340.5554638888889: "Libra",
304.3033805555556: "Scorpio",
231.04509444444443: "Capricorn",
51.045094444444445: "Cancer",
}
for longitude, expected_sign in expected.items():
assert calc_varga(longitude, 9)["sign"] == expected_sign
def test_64th_navamsa_counts_forward_from_moon_navamsa() -> None:
moon_lon = 42.3
result = calc_64th_navamsa(moon_lon)
expected_sign_idx = (navamsa_ref(moon_lon) + 63) % 12
assert result["sign_idx"] == expected_sign_idx
assert result["sign"] == SIGNS[expected_sign_idx]
assert result["offset_from_moon_navamsa"] == 64
def test_22nd_drekkana_counts_forward_from_lagna_drekkana() -> None:
asc_lon = 25.5
result = calc_22nd_drekkana(asc_lon)
expected_sign_idx = (drekkana_ref(asc_lon) + 21) % 12
assert result["sign_idx"] == expected_sign_idx
assert result["sign"] == SIGNS[expected_sign_idx]
assert result["offset_from_lagna_drekkana"] == 22
def test_bhrigu_bindu_uses_rahu_to_moon_arc_midpoint() -> None:
result = calc_bhrigu_bindu(270.5, 160.2333333333)
assert result["longitude"] == 215.3667
assert result["sign"] == "Scorpio"
assert result["degree_in_sign"] == 5.3667
def test_bhrigu_bindu_handles_wraparound_across_zero_aries() -> None:
result = calc_bhrigu_bindu(10.0, 350.0)
assert result["longitude"] == 0.0
assert result["sign"] == "Aries"
assert result["degree_in_sign"] == 0.0
def test_sarpa_drekkana_detects_classical_sensitive_ranges() -> None:
cancer_second = calc_sarpa_drekkana(105.0)
scorpio_first = calc_sarpa_drekkana(215.0)
pisces_third = calc_sarpa_drekkana(355.0)
safe_aries = calc_sarpa_drekkana(15.0)
assert cancer_second["is_sarpa_drekkana"] is True
assert cancer_second["sign"] == "Cancer"
assert cancer_second["drekkana_number"] == 2
assert scorpio_first["is_sarpa_drekkana"] is True
assert scorpio_first["sign"] == "Scorpio"
assert scorpio_first["drekkana_number"] == 1
assert pisces_third["is_sarpa_drekkana"] is True
assert pisces_third["sign"] == "Pisces"
assert pisces_third["drekkana_number"] == 3
assert safe_aries["is_sarpa_drekkana"] is False