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项目
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Shadbala Benchmark v1.0
|
||||
比较 v6.1.13 yinduzhanxing Shadbala 与 PyJHora 实现。
|
||||
|
||||
依赖: pip install jhora numpy
|
||||
"""
|
||||
import sys, json, os
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'scripts')))
|
||||
from shadbala import calc_shadbala, NAISARGIKA_BALA
|
||||
|
||||
# PyJHora
|
||||
from jhora import const, utils
|
||||
from jhora.panchanga import drik
|
||||
from jhora.horoscope.chart import charts
|
||||
from jhora.horoscope.strength import shadbala as pj_shadbala
|
||||
|
||||
TEST_CASES = [
|
||||
{"id": "beijing_1990_noon", "year":1990, "month":6, "day":15, "hour":12, "minute":0, "lat":39.9, "lon":116.4, "tz":8},
|
||||
{"id": "newyork_1985_morning", "year":1985, "month":3, "day":22, "hour":9, "minute":30, "lat":40.7, "lon":-74.0, "tz":-5},
|
||||
{"id": "london_1970_evening", "year":1970, "month":9, "day":12, "hour":19, "minute":0, "lat":51.5, "lon":-0.1, "tz":0},
|
||||
{"id": "delhi_2000_midnight", "year":2000, "month":1, "day":1, "hour":0, "minute":0, "lat":28.6, "lon":77.2, "tz":5.5},
|
||||
{"id": "sydney_1999_afternoon", "year":1999, "month":8, "day":7, "hour":15, "minute":45, "lat":-33.9, "lon":151.2, "tz":10},
|
||||
]
|
||||
|
||||
PLANET_NAMES = ['Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn']
|
||||
SIGNS = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo',
|
||||
'Libra', 'Scorpio', 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces']
|
||||
|
||||
|
||||
def run_benchmark():
|
||||
total_planets = 0
|
||||
within_10pct = 0
|
||||
within_20pct = 0
|
||||
details = []
|
||||
|
||||
for case in TEST_CASES:
|
||||
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)
|
||||
pp = charts.divisional_chart(jd, place, divisional_chart_factor=1)[:const._pp_count_upto_ketu]
|
||||
|
||||
# 提取行星位置
|
||||
asc_sign_idx = pp[0][1][0]
|
||||
sun_sign = pp[const.SUN_ID + 1][1][0]
|
||||
moon_sign = pp[const.MOON_ID + 1][1][1]
|
||||
sun_lon = sun_sign * 30 + pp[const.SUN_ID + 1][1][1]
|
||||
moon_lon = moon_sign
|
||||
|
||||
planets = {}
|
||||
for pid, pname in {const.SUN_ID: 'Sun', const.MOON_ID: 'Moon',
|
||||
const.MARS_ID: 'Mars', const.MERCURY_ID: 'Mercury',
|
||||
const.JUPITER_ID: 'Jupiter', const.VENUS_ID: 'Venus',
|
||||
const.SATURN_ID: 'Saturn'}.items():
|
||||
sign = pp[pid + 1][1][0]
|
||||
deg = pp[pid + 1][1][1]
|
||||
house = (sign - asc_sign_idx) % 12 + 1
|
||||
retro = pp[pid + 1][4] # retrograde flag in PyJHora
|
||||
speed = pp[pid + 1][3] # speed in degrees/day
|
||||
planets[pname] = {
|
||||
'sign': SIGNS[sign],
|
||||
'degree': sign * 30 + deg,
|
||||
'house': house,
|
||||
'retrograde': retro,
|
||||
'speed': speed,
|
||||
}
|
||||
|
||||
# 我们的Shadbala
|
||||
our = calc_shadbala(planets, SIGNS[asc_sign_idx],
|
||||
case["hour"] + case["minute"] / 60,
|
||||
sun_lon, moon_lon, case["minute"])
|
||||
|
||||
# PyJHora Shadbala
|
||||
try:
|
||||
pj = pj_shadbala.get_shadbala_scores(jd, place, divisional_chart_factor=1)
|
||||
except:
|
||||
print(f" PyJHora shadbala failed for {case['id']}")
|
||||
continue
|
||||
|
||||
case_match = 0
|
||||
case_total = 0
|
||||
case_details = []
|
||||
|
||||
for pname in PLANET_NAMES:
|
||||
if pname not in planets or pname not in our['planets']:
|
||||
continue
|
||||
|
||||
our_rupas = our['planets'][pname]['total_rupas']
|
||||
pj_rupas = pj.get(pname, {}).get('shadbala', {}).get('total', 0)
|
||||
if isinstance(pj_rupas, dict):
|
||||
pj_rupas = pj_rupas.get('rupas', 0)
|
||||
|
||||
total_planets += 1
|
||||
if pj_rupas > 0:
|
||||
diff_pct = abs(our_rupas - pj_rupas) / pj_rupas * 100
|
||||
if diff_pct <= 10:
|
||||
within_10pct += 1
|
||||
if diff_pct <= 20:
|
||||
within_20pct += 1
|
||||
case_details.append(f"{pname}: us={our_rupas:.2f}, pj={pj_rupas:.2f} ({diff_pct:.1f}%)")
|
||||
|
||||
details.append({
|
||||
'case': case['id'],
|
||||
'planets': case_details,
|
||||
})
|
||||
|
||||
print("=" * 80)
|
||||
print("Shadbala Benchmark v1.0")
|
||||
print(f"Total planets: {total_planets}")
|
||||
print("=" * 80)
|
||||
print(f"Within 10% of PyJHora: {within_10pct}/{total_planets} = {within_10pct/total_planets*100:.1f}%" if total_planets else "N/A")
|
||||
print(f"Within 20% of PyJHora: {within_20pct}/{total_planets} = {within_20pct/total_planets*100:.1f}%" if total_planets else "N/A")
|
||||
|
||||
for d in details:
|
||||
print(f"\n{d['case']}:")
|
||||
for pd in d['planets']:
|
||||
print(f" {pd}")
|
||||
|
||||
if within_20pct / total_planets >= 0.8 if total_planets else False:
|
||||
print("\n✅ PASS (≥80% within 20%)")
|
||||
else:
|
||||
print("\n⚠️ NEEDS IMPROVEMENT")
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_benchmark()
|
||||
@@ -0,0 +1,219 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user