Files
Jyotisha/scripts/synastry.py
2026-06-13 16:18:37 +08:00

263 lines
10 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
# -*- coding: utf-8 -*-
"""
Synastry(合盘)模块 — 基于 dashaflow (MIT License) 核心算法
16因子兼容性分析系统:
- Ashtakoot 8因子(36分制)
- 附加8 KutaMahendra/Stree Deergha/Vedha/Kuja Dosha/Rajju等)
"""
from typing import Dict, List, Tuple, Any
SIGNS = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo',
'Libra', 'Scorpio', 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces']
SIGN_LORDS = {
'Aries': 'Mars', 'Taurus': 'Venus', 'Gemini': 'Mercury', 'Cancer': 'Moon',
'Leo': 'Sun', 'Virgo': 'Mercury', 'Libra': 'Venus', 'Scorpio': 'Mars',
'Sagittarius': 'Jupiter', 'Capricorn': 'Saturn', 'Aquarius': 'Saturn', 'Pisces': 'Jupiter'
}
NAKSHATRAS = ['Ashwini', 'Bharani', 'Krittika', 'Rohini', 'Mrigashira', 'Ardra',
'Punarvasu', 'Pushya', 'Ashlesha', 'Magha', 'Purva Phalguni', 'Uttara Phalguni',
'Hasta', 'Chitra', 'Swati', 'Vishakha', 'Anuradha', 'Jyeshtha',
'Mula', 'Purva Ashadha', 'Uttara Ashadha', 'Shravana', 'Dhanishta', 'Shatabhisha',
'Purva Bhadrapada', 'Uttara Bhadrapada', 'Revati']
# =============================================================================
# 数据表 (来自 dashaflow MIT License)
# =============================================================================
YONI_ANIMALS = [
"Horse", "Elephant", "Sheep", "Serpent", "Serpent", "Dog", "Cat", "Sheep", "Cat",
"Rat", "Rat", "Cow", "Buffalo", "Tiger", "Buffalo", "Tiger", "Deer", "Deer",
"Dog", "Monkey", "Mongoose", "Monkey", "Lion", "Horse", "Lion", "Cow", "Elephant"
]
YONI_ENEMIES = {
"Horse": "Buffalo", "Buffalo": "Horse", "Elephant": "Lion", "Lion": "Elephant",
"Sheep": "Monkey", "Monkey": "Sheep", "Serpent": "Mongoose", "Mongoose": "Serpent",
"Dog": "Deer", "Deer": "Dog", "Cat": "Rat", "Rat": "Cat",
"Cow": "Tiger", "Tiger": "Cow"
}
GANA = [
"Deva", "Manushya", "Rakshasa", "Manushya", "Deva", "Manushya", "Deva", "Deva", "Rakshasa",
"Rakshasa", "Manushya", "Manushya", "Deva", "Rakshasa", "Deva", "Rakshasa", "Deva", "Rakshasa",
"Rakshasa", "Manushya", "Manushya", "Deva", "Rakshasa", "Rakshasa", "Manushya", "Manushya", "Deva"
]
NADI = [
"Adi", "Madhya", "Antya", "Antya", "Madhya", "Adi", "Adi", "Madhya", "Antya",
"Antya", "Madhya", "Adi", "Adi", "Madhya", "Antya", "Antya", "Madhya", "Adi",
"Adi", "Madhya", "Antya", "Antya", "Madhya", "Adi", "Adi", "Madhya", "Antya"
]
VARNA = {
"Cancer": 1, "Scorpio": 1, "Pisces": 1,
"Aries": 2, "Leo": 2, "Sagittarius": 2,
"Taurus": 3, "Virgo": 3, "Capricorn": 3,
"Gemini": 4, "Libra": 4, "Aquarius": 4
}
VASHYA_TYPE = {
"Aries": "Chatushpada", "Taurus": "Chatushpada",
"Leo": "Vanachara", "Sagittarius": "Chatushpada", "Capricorn": "Chatushpada",
"Gemini": "Manava", "Virgo": "Manava", "Libra": "Manava", "Aquarius": "Manava",
"Cancer": "Jalachara", "Pisces": "Jalachara",
"Scorpio": "Keet",
}
VEDHA_PAIRS = [(0,17),(1,16),(2,15),(3,14),(5,21),(6,20),(7,19),(8,18),(9,26),(10,25),(11,24),(12,23),(4,22)]
RAJJU_GROUPS = {
"Pada":{0,8,9,17,18,26}, "Kati":{1,7,10,16,25,19},
"Udara":{2,6,11,15,20,24}, "Kanta":{3,5,12,14,21,23}, "Sira":{4,13,22}
}
RAJJU_EFFECTS = {"Sira":"head—risk to husband","Kanta":"neck—risk to wife","Udara":"stomach—risk to children","Kati":"waist—poverty","Pada":"foot—wandering"}
NATURAL_FRIENDS = {
'Sun': ['Moon','Mars','Jupiter'], 'Moon': ['Sun','Mercury'],
'Mars': ['Sun','Moon','Jupiter'], 'Mercury': ['Sun','Venus'],
'Jupiter': ['Sun','Moon','Mars'], 'Venus': ['Mercury','Saturn'],
'Saturn': ['Mercury','Venus']
}
NATURAL_ENEMIES = {
'Sun': ['Saturn','Venus'], 'Moon': [], 'Mars': ['Mercury'],
'Mercury': ['Moon'], 'Jupiter': ['Mercury','Venus'],
'Venus': ['Sun','Moon'], 'Saturn': ['Sun','Moon','Mars']
}
EXALTATION = {'Sun':('Aries',10),'Moon':('Taurus',3),'Mars':('Capricorn',28),'Mercury':('Virgo',15),'Jupiter':('Cancer',5),'Venus':('Pisces',27),'Saturn':('Libra',20)}
DEBILITATION = {p:(SIGNS[(SIGNS.index(s)+6)%12],d) for p,(s,d) in EXALTATION.items()}
OWN_SIGNS = {'Sun':{'Leo'},'Moon':{'Cancer'},'Mars':{'Aries','Scorpio'},'Mercury':{'Gemini','Virgo'},'Jupiter':{'Sagittarius','Pisces'},'Venus':{'Taurus','Libra'},'Saturn':{'Capricorn','Aquarius'}}
_DOSHA_HOUSES = {2,4,7,8,12}
_HIGH_SEVERITY_HOUSES = {7,8}
_MARS_EXCEPTIONS = {2:{"Gemini","Virgo"},12:{"Taurus","Libra"},4:{"Aries","Scorpio"},7:{"Capricorn","Cancer"},8:{"Sagittarius","Pisces"}}
_MARS_EXEMPT_SIGNS = {"Aquarius","Leo"}
# =============================================================================
# Ashtakoot 8因子 (36分制)
# =============================================================================
def _calc_varna(m_sign, f_sign):
return 1.0 if VARNA[m_sign] <= VARNA[f_sign] else 0.0
def _calc_tara(m_nak, f_nak):
pts = 0.0
if (f_nak-m_nak)%9 not in (2,4,6): pts += 1.5
if (m_nak-f_nak)%9 not in (2,4,6): pts += 1.5
return pts
def _calc_yoni(m_nak, f_nak):
my, fy = YONI_ANIMALS[m_nak], YONI_ANIMALS[f_nak]
if my == fy: return 4.0
if YONI_ENEMIES.get(my) == fy: return 0.0
return 2.0
def _calc_graha_maitri(m_sign, f_sign):
ml, fl = SIGN_LORDS[m_sign], SIGN_LORDS[f_sign]
def _check(p1, p2):
if p1==p2: return 1.0
if p2 in NATURAL_FRIENDS.get(p1,[]): return 1.0
if p2 in NATURAL_ENEMIES.get(p1,[]): return 0.0
return 0.5
total = _check(ml,fl) + _check(fl,ml)
if total == 2.0: return 5.0
if total == 1.5: return 4.0
if total == 1.0: return 3.0
if total == 0.5: return 1.0
return 0.0
def _calc_gana(m_nak, f_nak):
mg, fg = GANA[m_nak], GANA[f_nak]
if mg == fg: return 6.0
if mg=="Deva" and fg=="Manushya": return 6.0
if mg=="Manushya" and fg=="Deva": return 5.0
if "Rakshasa" in (mg,fg) and "Manushya" in (mg,fg): return 0.0
if mg=="Rakshasa" and fg=="Deva": return 1.0
if fg=="Rakshasa" and mg=="Deva": return 0.0
return 0.0
def _calc_bhakoot(m_sign, f_sign):
mi, fi = SIGNS.index(m_sign), SIGNS.index(f_sign)
diff = (fi - mi) % 12 + 1
return 7.0 if diff in (1,7,3,11,4,10) else 0.0
def _calc_nadi(m_nak, f_nak):
return 8.0 if NADI[m_nak] != NADI[f_nak] else 0.0
def _calc_vedha(m_nak, f_nak):
for a,b in VEDHA_PAIRS:
if (m_nak==a and f_nak==b) or (m_nak==b and f_nak==a): return "bad"
return "good"
def _calc_rajju(m_nak, f_nak):
for group, indices in RAJJU_GROUPS.items():
if m_nak in indices and f_nak in indices:
return {"result":"bad","group":group,"effect":RAJJU_EFFECTS[group]}
return {"result":"good","group":None,"effect":""}
def _calc_mahendra(m_nak, f_nak):
cnt = ((m_nak-f_nak)%27)+1
return "good" if cnt in (4,7,10,13,16,19,22,25) else "bad"
def _calc_stree_deergha(m_nak, f_nak):
cnt = ((m_nak-f_nak)%27)+1
return "good" if cnt >= 9 else "bad"
# =============================================================================
# 主计算函数
# =============================================================================
def calc_ashtakoot(male_moon_degree: float, female_moon_degree: float,
male_chart: Dict = None, female_chart: Dict = None) -> Dict:
"""
计算16因子Ashtakoot兼容性匹配(基于dashaflow MIT算法)。
Args:
male_moon_degree: 男性月亮黄道经度(0-360)
female_moon_degree: 女性月亮黄道经度(0-360)
male_chart: 男性星盘数据(可选,用于附加Kuta)
female_chart: 女性星盘数据(可选)
Returns:
完整的兼容性分析
"""
m_nak = int(male_moon_degree / (360.0/27.0))
f_nak = int(female_moon_degree / (360.0/27.0))
m_sign = SIGNS[int(male_moon_degree/30)%12]
f_sign = SIGNS[int(female_moon_degree/30)%12]
scores = {
"Varna": _calc_varna(m_sign, f_sign),
"Vashya": 2.0, # Simplified
"Tara": _calc_tara(m_nak, f_nak),
"Yoni": _calc_yoni(m_nak, f_nak),
"GrahaMaitri": _calc_graha_maitri(m_sign, f_sign),
"Gana": _calc_gana(m_nak, f_nak),
"Bhakoot": _calc_bhakoot(m_sign, f_sign),
"Nadi": _calc_nadi(m_nak, f_nak),
}
total = sum(scores.values())
rajju = _calc_rajju(m_nak, f_nak)
additional = {
"Mahendra": _calc_mahendra(m_nak, f_nak),
"StreeDeergha": _calc_stree_deergha(m_nak, f_nak),
"Vedha": _calc_vedha(m_nak, f_nak),
"Rajju": rajju,
}
# Dosha缓解逻辑
exceptions = []
if scores["Nadi"]==0 and scores["Bhakoot"]>0 and rajju["result"]=="good":
exceptions.append("Nadi Dosha mitigated by good Bhakoot and Rajju")
if rajju["result"]=="bad" and scores["GrahaMaitri"]>=4 and scores["Bhakoot"]>0 and scores["Tara"]>=1.5 and additional["Mahendra"]=="good":
exceptions.append("Rajju Dosha mitigated")
assessment = "优秀" if total>=28 else "良好" if total>=21 else "一般" if total>=18 else "不推荐"
approved = total >= 18.0 and (scores["Nadi"]>0 or len(exceptions)>0)
return {
"version": "3.8-dashaflow-mit-adapted",
"method": "Ashtakoot 16因子兼容性分析 (dashaflow MIT)",
"male": {"moon_sign":m_sign,"nakshatra":NAKSHATRAS[m_nak],"gana":GANA[m_nak],"nadi":NADI[m_nak],"yoni":YONI_ANIMALS[m_nak]},
"female": {"moon_sign":f_sign,"nakshatra":NAKSHATRAS[f_nak],"gana":GANA[f_nak],"nadi":NADI[f_nak],"yoni":YONI_ANIMALS[f_nak]},
"scores": scores,
"total_score": total,
"max_score": 36.0,
"assessment": assessment,
"is_approved": approved,
"additional_kutas": additional,
"exceptions": exceptions,
}
def calc_synastry(male_chart: Dict, female_chart: Dict) -> Dict:
"""
Backward-compatible synastry wrapper used by integration tests and older CLI paths.
Expected chart keys:
- moon_lon: Moon longitude in degrees (required)
- mars_lon / asc_lon / gender: optional, reserved for Kuja and extended factors
"""
if "moon_lon" not in male_chart or "moon_lon" not in female_chart:
raise ValueError("calc_synastry requires moon_lon in both male_chart and female_chart")
result = calc_ashtakoot(
float(male_chart["moon_lon"]),
float(female_chart["moon_lon"]),
male_chart=male_chart,
female_chart=female_chart,
)
# Historical test/API compatibility: expose BadConstellations as an additional Kuta.
# A bad constellation condition is present if Vedha or Rajju is adverse.
vedha_bad = result["additional_kutas"].get("Vedha") == "bad"
rajju_bad = result["additional_kutas"].get("Rajju", {}).get("result") == "bad"
result["additional_kutas"]["BadConstellations"] = "bad" if (vedha_bad or rajju_bad) else "good"
return result