Files
Jyotisha/benchmarks/jyotish/scripts/run_vimshottari_compare.py
732642856 a4f8620a71 v6.2.0: 全面技法宝库升级 — 16个新模块 + 12个文件优化
## 新增模块 (16个)
### P0 精度修复
- ashtakavarga: calc_prastara_av() + calc_sodhita_av()
- kakshya.py: Kakshya评分系统 (8区间×3.75°)
- shadbala.py: Sputa Drishti + Yuddha Bala

### P1 核心升级
- bhava_bala.py: 宫位三元力量 (jyotishganit MIT)
- pancha_mahapurusha.py: PMC完整检测含4层失效条件
- sade_sati.py: Sade Sati+Kantaka Shani
- sudarshana_chakra.py: 三参考点盘+收敛分析
- tajika.py: Sahams 7→36 + Tajika Yogas 10种
- birth_time_rectifier.py: 生时矫正

### P2 覆盖扩展
- kp_system.py: KP Sublord+ABCD Significator (diliprk/VedicAstro MIT)
- synastry.py: 16因子合盘36分制 (dashaflow MIT)
- muhurtha_election.py: 6活动选举 (dashaflow MIT)
- career_analysis.py: 结构化事业引擎
- relationship_analysis.py: 结构化感情引擎
- conditional_dashas.py: Dwisaptati+Shattrimsa+Dwadashottari
- divisional_charts_extended: D81/D108/D144
- remedies.py: 5类补救系统

## 修改文件
jaimini/dasha_calculator/shadbala/SKILL.md/COVERAGE_AUDIT等12个

## 开源复用: 4个MIT项目
2026-06-11 19:03:21 +08:00

220 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.
#!/usr/bin/env python3
"""
Vimshottari Dasha Benchmark v1.1
比较 v6.1.13 yinduzhanxing Vimshottari Dasha 与 PyJHora 实现。
算法来源:基于jyotishganit (MIT License) 核心算法。
使用恒星年 = 365.25636天。
对比字段:
- MD主星(前3个)、MD起始/结束日期
- AD主星(第1个MD的前3个AD)、AD起始日期
依赖: pip install jhora numpy
"""
import sys, json, os
from datetime import datetime, timedelta
# 添加scripts路径
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'scripts')))
from dasha_calculator_enhanced import (
calculate_dasha_dates, calculate_dasha_start_date,
DASHA_ORDER, VIMSHOTTARI_PERIODS, HUMAN_LIFE_SPAN_VIMSHOTTARI,
YEAR_DURATION_DAYS
)
# PyJHora
from jhora.horoscope.dhasa.graha import vimsottari as pj_vimshottari
from jhora.panchanga import drik
from jhora import utils, const
from jhora.horoscope.chart import charts
# ─────────────────────────────────────────────
# 10个测试案例
# ─────────────────────────────────────────────
TEST_CASES = [
{"id": "smoke_beijing_1990_noon", "year":1990, "month":6, "day":15, "hour":12, "minute":0, "lat":39.9, "lon":116.4, "tz":8},
{"id": "smoke_newyork_1985_morning", "year":1985, "month":3, "day":22, "hour":9, "minute":30, "lat":40.7, "lon":-74.0, "tz":-5},
{"id": "smoke_london_1970_evening", "year":1970, "month":9, "day":12, "hour":19, "minute":0, "lat":51.5, "lon":-0.1, "tz":0},
{"id": "smoke_delhi_2000_midnight", "year":2000, "month":1, "day":1, "hour":0, "minute":0, "lat":28.6, "lon":77.2, "tz":5.5},
{"id": "smoke_sydney_1999_afternoon", "year":1999, "month":8, "day":7, "hour":15, "minute":45, "lat":-33.9, "lon":151.2, "tz":10},
{"id": "smoke_tokyo_1964_noon", "year":1964, "month":10, "day":10,"hour":12, "minute":0, "lat":35.7, "lon":139.7, "tz":9},
{"id": "smoke_cairo_1952_dawn", "year":1952, "month":7, "day":23, "hour":6, "minute":0, "lat":30.0, "lon":31.2, "tz":2},
{"id": "smoke_paris_1989_noon", "year":1989, "month":11,"day":9, "hour":12, "minute":30, "lat":48.9, "lon":2.3, "tz":1},
{"id": "smoke_losangeles_1995_night", "year":1995, "month":2, "day":14, "hour":22, "minute":15, "lat":34.1, "lon":-118.2,"tz":-8},
{"id": "smoke_sao_paulo_2004_morning","year":2004, "month":12,"day":25, "hour":7, "minute":0, "lat":-23.5, "lon":-46.6, "tz":-3},
]
# PyJHora行星ID -> 名称
PLANET_ID_TO_NAME = {0: 'Ketu', 1: 'Venus', 2: 'Sun', 3: 'Moon',
4: 'Mars', 5: 'Rahu', 6: 'Jupiter', 7: 'Saturn', 8: 'Mercury'}
def jd_to_datetime(jd):
"""Julian day -> datetime"""
unix_epoch_jd = 2440587.5
seconds_since_epoch = (jd - unix_epoch_jd) * 86400
return datetime(1970, 1, 1) + timedelta(seconds=seconds_since_epoch)
def run_benchmark():
total_fields = 0
match_fields = 0
detail_lines = []
sample_details = []
for case in TEST_CASES:
# 1. 准备PyJHora数据
dob = drik.Date(case["year"], case["month"], case["day"])
tob = (case["hour"], case["minute"], 0)
place = drik.Place(f'test_{case["id"]}', case["lat"], case["lon"], case["tz"])
jd = utils.julian_day_number(dob, tob)
birth_date = datetime(case["year"], case["month"], case["day"],
case["hour"], case["minute"])
# 2. 获取Moon经度(来自PyJHora
pp = charts.divisional_chart(jd, place, divisional_chart_factor=1)[:const._pp_count_upto_ketu]
moon_sign = pp[const.MOON_ID + 1][1][0]
moon_deg = pp[const.MOON_ID + 1][1][1]
moon_degree = moon_sign * 30 + moon_deg
# 3. 我们的Vimshottari计算
our_md_list = calculate_dasha_dates(birth_date, moon_degree)
# 4. PyJHora Vimshottari MD
pj_md_dict = pj_vimshottari.vimsottari_mahadasa(jd, place, divisional_chart_factor=1)
pj_md_ordered = list(pj_md_dict.items())[:3]
case_match = 0
case_total = 0
mismatches = []
# 比较MD(前3个)- lord + start date
for i in range(min(3, len(our_md_list), len(pj_md_ordered))):
our_md = our_md_list[i]
pj_lord_id, pj_start_jd = pj_md_ordered[i]
# MD lord
case_total += 1
our_lord = our_md['lord']
pj_lord = PLANET_ID_TO_NAME.get(pj_lord_id, str(pj_lord_id))
if our_lord == pj_lord:
case_match += 1
else:
mismatches.append(f"MD[{i}] lord: us={our_lord}, pj={pj_lord}")
# MD start date(比较MD起始时间)
case_total += 1
our_start = our_md['start_date']
pj_start = jd_to_datetime(pj_start_jd)
diff_days = abs((our_start - pj_start).days)
if diff_days <= 90: # 3个月容差(由于恒星年 vs 回归年等差异)
case_match += 1
else:
mismatches.append(f"MD[{i}] start: us={our_start.strftime('%Y-%m-%d')}, pj={pj_start.strftime('%Y-%m-%d')} (diff={diff_days}d)")
# 比较AD(第1个MD的前3个AD
if len(our_md_list) > 0 and len(pj_md_ordered) > 0:
our_first_md = our_md_list[0]
our_md_total_days = our_first_md['years'] * YEAR_DURATION_DAYS
pj_first_lord_id, pj_first_start_jd = pj_md_ordered[0]
pj_ad_dict = pj_vimshottari._vimsottari_bhukti(pj_first_lord_id, pj_first_start_jd)
pj_ad_ordered = list(pj_ad_dict.items())[:3]
for j in range(min(3, len(pj_ad_ordered))):
pj_ad_lord_id, pj_ad_start_jd = pj_ad_ordered[j]
# AD lord
case_total += 1
pj_ad_lord = PLANET_ID_TO_NAME.get(pj_ad_lord_id, str(pj_ad_lord_id))
# 我们的AD lord按Vimshottari顺序
our_first_lord = our_first_md['lord']
first_idx = DASHA_ORDER.index(our_first_lord)
ad_idx = (first_idx + j) % 9
our_ad_lord = DASHA_ORDER[ad_idx]
if our_ad_lord == pj_ad_lord:
case_match += 1
else:
mismatches.append(f"AD[0,{j}] lord: us={our_ad_lord}, pj={pj_ad_lord}")
# AD start date
case_total += 1
pj_ad_start = jd_to_datetime(pj_ad_start_jd)
# 我们的AD start - 基于比例公式
our_ad_start = our_first_md['start_date']
for k in range(j):
ad_lord_dur = VIMSHOTTARI_PERIODS[DASHA_ORDER[(first_idx + k) % 9]]
ad_days = our_md_total_days * (ad_lord_dur / HUMAN_LIFE_SPAN_VIMSHOTTARI)
our_ad_start += timedelta(days=ad_days)
diff_days = abs((our_ad_start - pj_ad_start).days)
if diff_days <= 90:
case_match += 1
else:
mismatches.append(f"AD[0,{j}] start: us={our_ad_start.strftime('%Y-%m-%d')}, pj={pj_ad_start.strftime('%Y-%m-%d')} (diff={diff_days}d)")
total_fields += case_total
match_fields += case_match
sample_details.append({
'case': case['id'],
'match': f"{case_match}/{case_total}",
'mismatches': mismatches[:5],
})
# ── 输出 ──
print("=" * 80)
print("Vimshottari Dasha Benchmark v1.1")
print(f"Skill: v6.1.13 (基于jyotishganit MIT算法 + 恒星年={YEAR_DURATION_DAYS:.5f})")
print("=" * 80)
print(f"\n{'Case':40s} | Match")
print("-" * 80)
for d in sample_details:
print(f" {d['case']:35s} | {d['match']:>10s}")
print("-" * 80)
rate = match_fields / total_fields * 100 if total_fields else 0
print(f"\nTotal Match: {match_fields}/{total_fields} = {rate:.2f}%")
if rate >= 95:
status = "✅ PASS ✓"
elif rate >= 85:
status = "⚠️ BORDERLINE"
else:
status = "❌ FAIL"
print(f"\nBenchmark Result: {status}")
if status != "✅ PASS ✓":
print("\n--- 不匹配详情 ---")
for d in sample_details:
if d["mismatches"]:
print(f"\n{d['case']}:")
for m in d["mismatches"][:3]:
print(f" {m}")
# 保存 JSON
out = os.path.join(os.path.dirname(__file__), "..", "outputs", "vimshottari_benchmark.json")
os.makedirs(os.path.dirname(out), exist_ok=True)
result = {
"version": "v6.1.13",
"benchmark": "vimshottari_dasha",
"total_match": match_fields,
"total_fields": total_fields,
"match_rate": round(rate / 100, 4) if total_fields else 0,
"status": status,
"samples": sample_details,
}
with open(out, 'w') as f:
json.dump(result, f, indent=2, ensure_ascii=False)
print(f"\n结果已保存: {out}")
if __name__ == "__main__":
run_benchmark()