Files
Jyotisha/tests/test_shadbala_jhora_benchmark.py
2026-07-08 17:03:04 +08:00

235 lines
8.9 KiB
Python
Raw Permalink 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.
"""
JHora 基准校准测试套件 v1.0
对比当前引擎的 Shadbala 输出与 JHora/PyJHora 的已知参考值
测试数据集来源:
- BV Raman (作者本人) — PyJHora 标准测试用例
- PVR Rao — PyJHora PVR 教材测试用例
"""
import json
import sys
import os
# 确保可以导入 scripts 模块
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "scripts"))
from shadbala import calc_shadbala
# ============================================================================
# 标准测试数据集
# ============================================================================
# 来源:PyJHora test suite + PVR 教材
TEST_CASES = [
{
"name": "BV Raman (作者本人)",
"data": {
"year": 1918, "month": 10, "day": 16,
"hour": 14, "minute": 22, "second": 16,
"lat": 12.97, "lon": 77.58, # 班加罗尔
"tz": 5.5,
},
# 预期值来源:PyJHora Drik Bala for BV Raman (Raman Ayanamsa)
"expected": {
"drik_bala_sunny": 15.86, # 示例值,需从 JHora 确认
}
},
{
"name": "PVR Rao (教材示例)",
"data": {
"year": 1981, "month": 9, "day": 13,
"hour": 1, "minute": 30, "second": 0,
"lat": 28.65, "lon": 77.22, # 德里
"tz": 5.5,
},
"expected": {}
},
]
# ============================================================================
# 参考值:BPHS Shadbala 最低要求(Rupas
# ============================================================================
BPHS_MIN_REQUIRED = {
"Sun": 5.0, "Moon": 6.0, "Mars": 5.0,
"Mercury": 7.0, "Jupiter": 6.5, "Venus": 5.5, "Saturn": 5.0
}
# PyJHora 使用的 Naisargika BalaShashtiamshas
NAISARGIKA_REF = {
"Sun": 60.0, "Moon": 51.43, "Venus": 42.86,
"Jupiter": 34.29, "Mercury": 25.71, "Mars": 17.14, "Saturn": 8.57
}
# ============================================================================
# 内部一致性测试:检查 Shadbala 子项和是否等于总分
# ============================================================================
def _check_internal_consistency(result):
"""检查每个行星的6个子项之和是否等于 total_virupas"""
errors = []
for pname, pd in result.get("planets", {}).items():
sthana_total = pd.get("sthana_bala", {}).get("total", 0)
dig = pd.get("dig_bala", 0)
kala_total = pd.get("kala_bala", {}).get("total", 0)
chesta = pd.get("chesta_bala", 0)
naisargika = pd.get("naisargika_bala", 0)
drik = pd.get("drik_bala", 0)
total = pd.get("total_virupas", 0)
computed = sthana_total + dig + kala_total + chesta + naisargika + drik
diff = abs(computed - total)
if diff > 0.1:
errors.append(f"{pname}: 子项和={computed:.2f} ≠ total_virupas={total:.2f} (差{diff:.2f})")
return errors
# ============================================================================
# 外部一致性测试:检查是否满足 BPHS 最低要求
# ============================================================================
def _check_min_required(result):
"""检查每个行星是否满足 BPHS 最低 Shadbala Rupas 要求"""
errors = []
for pname, pd in result.get("planets", {}).items():
total_rupas = pd.get("total_rupas", 0)
min_req = BPHS_MIN_REQUIRED.get(pname, 5.0)
if total_rupas < min_req:
errors.append(f"{pname}: {total_rupas:.2f} Rupas < 最低要求 {min_req:.2f}")
return errors
# ============================================================================
# Naisargika Bala 对比
# ============================================================================
def _check_naisargika(result):
"""检查 Naisargika Bala 是否匹配参考值"""
errors = []
for pname, pd in result.get("planets", {}).items():
actual = pd.get("naisargika_bala", 0)
expected = NAISARGIKA_REF.get(pname, 0)
if abs(actual - expected) > 0.1:
errors.append(f"{pname}: Naisargika={actual:.2f} ≠ 参考值{expected:.2f}")
return errors
# ============================================================================
# 排序合理性测试
# ============================================================================
def _check_ranking_sanity(result):
"""检查排名是否合理(擢升行星应比落陷行星强)"""
planets = result.get("planets", {})
ranking = result.get("ranking", [])
errors = []
# 如果有擢升行星,应排在非擢升行星之前
if len(ranking) >= 2:
pass # 无法自动判断合理排序,只做记录
return errors
# ============================================================================
# 运行全部测试
# ============================================================================
def run_all_tests():
results_summary = []
total_tests = 0
total_errors = 0
for tc in TEST_CASES:
d = tc["data"]
print(f"\n{'='*60}")
print(f"测试: {tc['name']}")
print(f" 出生: {d['year']}-{d['month']:02d}-{d['day']:02d} {d['hour']:02d}:{d['minute']:02d}")
print(f" 地点: {d['lat']}°, {d['lon']}°, tz={d['tz']}")
print(f"{'='*60}")
# 简化:不实际调用占星引擎(引擎需要完整行星数据)
# 实际运行需要先计算 chart 获取完整行星数据
# 这里先占位,后续接入 jyotish_engine.py 的 calculate_full_chart
case_result = {
"name": tc["name"],
"tests": {},
}
# 测试1: 内部一致性
# TODO: 接入完整计算链后启用
# errors = test_internal_consistency(result)
# 测试2: Naisargika Bala 对比
# TODO: 接入
results_summary.append(case_result)
return results_summary
# ============================================================================
# 手动单用例测试函数(用于快速验证特定命盘)
# ============================================================================
def verify_shadbala_for_chart(planets, asc_sign, birth_hour, sun_lon, moon_lon,
birth_minute=0, chart_name="测试盘"):
"""对给定命盘运行完整的 Shadbala 验证"""
result = calc_shadbala(planets, asc_sign, birth_hour, sun_lon, moon_lon,
birth_minute)
print(f"\n{'='*60}")
print(f"Shadbala 验证: {chart_name}")
print(f"{'='*60}")
print(f"方法: {result['method']}")
print(f"排名: {' > '.join(result['ranking'])}")
print(f"最强: {result['strongest']}, 最弱: {result['weakest']}")
print(f"\n{'行星':>8} | {'Rupas':>6} | {'最低要求':>8} | {'达标?':>5} | {'Chesta':>6} | {'Ishta':>6} | {'Kashta':>6}")
print("-" * 65)
errors = []
for pname in ['Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn']:
pd = result.get("planets", {}).get(pname, {})
rupas = pd.get("total_rupas", 0)
min_req = BPHS_MIN_REQUIRED.get(pname, 5.0)
passed = "" if rupas >= min_req else ""
chesta = pd.get("chesta_bala", 0)
ishta = pd.get("ishta_phala", 0)
kashta = pd.get("kashta_phala", 0)
print(f"{pname:>8} | {rupas:>6.2f} | {min_req:>8.2f} | {passed:>5} | {chesta:>6.1f} | {ishta:>6.1f} | {kashta:>6.1f}")
if rupas < min_req:
errors.append(f"{pname}: {rupas:.2f} < {min_req:.2f}")
# 内部一致性测试
consistency = _check_internal_consistency(result)
for e in consistency:
errors.append(e)
# Naisargika 测试
naisargika = _check_naisargika(result)
for e in naisargika:
errors.append(e)
print("-" * 65)
if errors:
print(f"\n⚠️ 发现 {len(errors)} 个问题:")
for e in errors:
print(f"{e}")
else:
print(f"\n✅ 全部检查通过")
return result, errors
if __name__ == "__main__":
# 使用PRIVATE_REDACTED_CASE的出生数据运行验证
planets = {
'Sun': {'sign': 'Aries', 'degree': 27.5, 'house': 9, 'retrograde': False, 'speed': 0.98},
'Moon': {'sign': 'Aquarius', 'degree': 10.0, 'house': 7, 'retrograde': False, 'speed': 13.5},
'Mars': {'sign': 'Cancer', 'degree': 5.0, 'house': 12, 'retrograde': False, 'speed': 0.65},
'Mercury': {'sign': 'Pisces', 'degree': 22.0, 'house': 8, 'retrograde': False, 'speed': 1.2},
'Jupiter': {'sign': 'Virgo', 'degree': 15.0, 'house': 2, 'retrograde': True, 'speed': -0.08},
'Venus': {'sign': 'Pisces', 'degree': 5.0, 'house': 8, 'retrograde': False, 'speed': 1.1},
'Saturn': {'sign': 'Aquarius', 'degree': 28.0, 'house': 7, 'retrograde': False, 'speed': 0.05},
}
verify_shadbala_for_chart(
planets, 'Leo', 14.75, 27.5, 310.0,
birth_minute=45,
chart_name="PRIVATE_REDACTED_CASE (public sample birth datetime public sample)"
)