Files
Jyotisha/scripts/sudarshana_chakra.py
2026-06-13 15:06:11 +08:00

588 lines
22 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Sudarshana Chakra(苏达沙那轮)— 三参考点复合分析
====================================================
基于BPHS传统技法,将星盘分别以上升、月亮、太阳为第一宫,
生成三张参考盘并叠加分析。
三个参考点:
1. Ascendant Lagna (AL) → 自我、身体
2. Moon Lagna (ML) → 情感、心理
3. Sun Lagna (SL) → 灵魂、生命力
当三个参考点中同一宫位/行星配置一致时,事件确认度高。
版本: v2.0 | 2026-06-13 重构为完整分析器
"""
from typing import Dict, List, Optional, Tuple
SIGNS = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo',
'Libra', 'Scorpio', 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces']
SIGNS_CN = {
'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'
}
PLANETS_ALL = ['Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn', 'Rahu', 'Ketu']
# 吉宫: 1,2,3,4,5,7,9,10,11 凶宫: 6,8,12
FAVORABLE_HOUSES = {1, 2, 3, 4, 5, 7, 9, 10, 11}
UNFAVORABLE_HOUSES = {6, 8, 12}
# 中性宫: 无(传统分法中6,8,12为dusthana,其余吉)
HOUSE_MEANINGS = {
1: ('自我/健康', 'Self/Health'),
2: ('财富/家庭', 'Wealth/Family'),
3: ('勇气/兄弟姐妹', 'Courage/Siblings'),
4: ('幸福/母亲/住所', 'Happiness/Mother/Home'),
5: ('子女/智力/过去善业', 'Children/Intelligence/Poorvapunya'),
6: ('疾病/敌人/债务', 'Disease/Enemies/Debt'),
7: ('婚姻/伴侣/合作', 'Marriage/Partnership'),
8: ('寿命/变革/隐秘', 'Longevity/Transformation'),
9: ('幸运/导师/宗教', 'Fortune/Guru/Religion'),
10: ('事业/地位/名声', 'Career/Status/Fame'),
11: ('收益/愿望/朋友圈', 'Gains/Wishes/Circles'),
12: ('损失/解脱/海外', 'Loss/Liberation/Foreign'),
}
# 宫主星飞入各宫的吉凶权重
LORD_PLACEMENT_WEIGHTS = {
# 自身宫位 → 强
'own_house': 1.0,
# 吉宫
'favorable': 0.8,
# 凶宫
'unfavorable': 0.2,
# 角宫(kendra) 1,4,7,10
'kendra': 0.7,
# 三方宫(trikona) 1,5,9
'trikona': 0.9,
}
class SudarshanaChakraAnalyzer:
"""Sudarshana Chakra — 三 Lagna 叠加分析器"""
SIGNS = SIGNS
SIGN_LORDS = SIGN_LORDS
def __init__(self):
self.seven_planets = ['Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn']
# ------------------------------------------------------------------
# 内部工具方法
# ------------------------------------------------------------------
@staticmethod
def _sign_idx(sign_or_idx) -> int:
"""将星座名或索引统一转为 0-11 索引"""
if isinstance(sign_or_idx, int):
return sign_or_idx % 12
if isinstance(sign_or_idx, str) and sign_or_idx in SIGNS:
return SIGNS.index(sign_or_idx)
return 0
@staticmethod
def _house_from_refs(planet_sign_idx: int, reference_sign_idx: int) -> int:
"""计算行星在以 reference_sign_idx 为第一宫时的宫位 (1-12)"""
return (planet_sign_idx - reference_sign_idx) % 12 + 1
@staticmethod
def _sign_idx_from_lon(lon: float) -> int:
"""从黄经获取星座索引 (0-11)"""
return int(lon / 30) % 12
def _planet_sign_idx(self, planet_lons: Dict, planet: str) -> int:
"""从 planet_lons 字典获取行星星座索引"""
lon = planet_lons.get(planet)
if lon is None:
return 0
return self._sign_idx_from_lon(lon)
# ------------------------------------------------------------------
# 核心方法
# ------------------------------------------------------------------
def generate_three_charts(self, planet_lons: Dict, asc_lon: float) -> Dict:
"""
生成三参考点盘。
Args:
planet_lons: {planet_name: longitude_in_sidereal_0_360}
asc_lon: 上升点黄经 (sidereal, 0-360)
Returns:
{
'ascendant_lagna': {planet: {'sign': str, 'sign_idx': int, 'house': int, 'degree': float}},
'moon_lagna': {planet: {'sign': str, 'sign_idx': int, 'house': int, 'degree': float}},
'sun_lagna': {planet: {'sign': str, 'sign_idx': int, 'house': int, 'degree': float}},
}
"""
asc_idx = self._sign_idx_from_lon(asc_lon)
moon_idx = self._planet_sign_idx(planet_lons, 'Moon')
sun_idx = self._planet_sign_idx(planet_lons, 'Sun')
refs = {
'ascendant_lagna': asc_idx,
'moon_lagna': moon_idx,
'sun_lagna': sun_idx,
}
result = {}
for chart_name, ref_idx in refs.items():
chart = {}
for planet, lon in planet_lons.items():
p_sign_idx = self._sign_idx_from_lon(lon)
house = self._house_from_refs(p_sign_idx, ref_idx)
chart[planet] = {
'sign': SIGNS[p_sign_idx],
'sign_cn': SIGNS_CN[SIGNS[p_sign_idx]],
'sign_idx': p_sign_idx,
'house': house,
'degree': round(lon, 4),
'degree_in_sign': round(lon - p_sign_idx * 30, 4),
}
result[chart_name] = chart
return result
def composite_analysis(self, planet_lons: Dict, asc_lon: float) -> Dict:
"""
分析每个行星在三张盘中的吉凶强度。
对于每颗行星:
- 统计三张盘中落入吉宫(1,2,3,4,5,7,9,10,11)的次数
- 统计落入凶宫(6,8,12)的次数
- 复合评分: favorable_count / 3 (0.0 ~ 1.0)
Returns:
{planet: {
'asc_house': int, 'moon_house': int, 'sun_house': int,
'favorable_count': int (0-3),
'unfavorable_count': int (0-3),
'composite_score': float (0.0-1.0),
'interpretation': str
}}
"""
charts = self.generate_three_charts(planet_lons, asc_lon)
result = {}
for planet in planet_lons:
if planet not in charts['ascendant_lagna']:
continue
asc_h = charts['ascendant_lagna'][planet]['house']
moon_h = charts['moon_lagna'][planet]['house']
sun_h = charts['sun_lagna'][planet]['house']
houses = [asc_h, moon_h, sun_h]
fav = sum(1 for h in houses if h in FAVORABLE_HOUSES)
unfav = sum(1 for h in houses if h in UNFAVORABLE_HOUSES)
score = fav / 3.0
if fav == 3:
interp = "三盘皆吉 — 该领域高度确认,力量极强"
elif fav == 2 and unfav == 0:
interp = "两盘吉、一中性 — 总体有利"
elif fav == 2 and unfav == 1:
interp = "两盘吉、一凶 — 有利但有隐患"
elif fav == 1 and unfav == 2:
interp = "一盘吉、两盘凶 — 矛盾信号,需结合大运判断"
elif unfav == 3:
interp = "三盘皆凶 — 该领域挑战极大"
elif fav == 1 and unfav == 1:
interp = "一吉一凶一中性 — 混合信号"
elif unfav == 2:
interp = "两盘凶 — 力量偏弱"
else:
interp = "中性 — 无明显吉凶偏向"
result[planet] = {
'asc_house': asc_h,
'moon_house': moon_h,
'sun_house': sun_h,
'favorable_count': fav,
'unfavorable_count': unfav,
'composite_score': round(score, 3),
'interpretation': interp,
}
return result
def house_analysis(self, planet_lons: Dict, asc_lon: float, house_number: int) -> Dict:
"""
分析指定宫位在三张盘中的情况。
Returns:
{
'house': int,
'meaning_cn': str,
'meaning_en': str,
'asc_lagna': {'planets': [...], 'lord': str, 'lord_house': int},
'moon_lagna': {'planets': [...], 'lord': str, 'lord_house': int},
'sun_lagna': {'planets': [...], 'lord': str, 'lord_house': int},
'composite_strength': float (0.0-1.0),
'interpretation': str
}
"""
if not 1 <= house_number <= 12:
return {'error': f'宫位号 {house_number} 超出范围(1-12)'}
charts = self.generate_three_charts(planet_lons, asc_lon)
def _analyze_house(chart: Dict, ref_sign_idx: int) -> Dict:
"""分析单个参考盘中某宫位"""
# 该宫位对应的星座
house_sign_idx = (ref_sign_idx + house_number - 1) % 12
house_sign = SIGNS[house_sign_idx]
lord = SIGN_LORDS[house_sign]
# 该宫位中的行星
planets_in_house = []
for pname, pdata in chart.items():
if pdata['house'] == house_number:
planets_in_house.append(pname)
# 宫主星所在宫位
lord_sign_idx = self._planet_sign_idx(planet_lons, lord) if lord in planet_lons else None
lord_house = self._house_from_refs(lord_sign_idx, ref_sign_idx) if lord_sign_idx is not None else None
return {
'sign': house_sign,
'sign_cn': SIGNS_CN[house_sign],
'lord': lord,
'lord_house': lord_house,
'planets': planets_in_house,
}
asc_idx = self._sign_idx_from_lon(asc_lon)
moon_idx = self._planet_sign_idx(planet_lons, 'Moon')
sun_idx = self._planet_sign_idx(planet_lons, 'Sun')
al = _analyze_house(charts['ascendant_lagna'], asc_idx)
ml = _analyze_house(charts['moon_lagna'], moon_idx)
sl = _analyze_house(charts['sun_lagna'], sun_idx)
# 复合强度评分
strength_components = []
for ref_data in [al, ml, sl]:
s = 0.0
# 宫内有行星加分(尤其吉星)
for p in ref_data['planets']:
if p in ('Jupiter', 'Venus', 'Moon', 'Mercury'):
s += 0.2
elif p in ('Saturn', 'Mars', 'Rahu', 'Ketu'):
s += 0.05
else:
s += 0.1
# 宫主星落入吉宫加分
lh = ref_data.get('lord_house')
if lh:
if lh in FAVORABLE_HOUSES:
s += 0.3
elif lh in UNFAVORABLE_HOUSES:
s += 0.05
else:
s += 0.15
strength_components.append(min(s, 1.0))
composite_strength = round(sum(strength_components) / 3.0, 3)
# 解读
if composite_strength >= 0.7:
interp = f"{house_number}宫整体强势,三盘均支持该领域发展"
elif composite_strength >= 0.4:
interp = f"{house_number}宫中等强度,部分参考盘有利,部分偏弱"
else:
interp = f"{house_number}宫整体偏弱,该领域需更多努力与补救"
meaning_cn, meaning_en = HOUSE_MEANINGS.get(house_number, ('未知', 'Unknown'))
return {
'house': house_number,
'meaning_cn': meaning_cn,
'meaning_en': meaning_en,
'asc_lagna': al,
'moon_lagna': ml,
'sun_lagna': sl,
'composite_strength': composite_strength,
'interpretation': interp,
}
def life_area_analysis(self, planet_lons: Dict, asc_lon: float) -> Dict:
"""
12个生活领域的综合分析。
Returns:
{area_name: {house: int, meaning_cn: str, composite_strength: float, details: dict, verdict: str}}
"""
areas = {}
for h in range(1, 13):
ha = self.house_analysis(planet_lons, asc_lon, h)
meaning_cn = ha.get('meaning_cn', f'{h}')
strength = ha['composite_strength']
if strength >= 0.7:
verdict = ''
elif strength >= 0.5:
verdict = '中上'
elif strength >= 0.35:
verdict = ''
elif strength >= 0.2:
verdict = '偏弱'
else:
verdict = ''
areas[meaning_cn] = {
'house': h,
'meaning_cn': meaning_cn,
'meaning_en': ha.get('meaning_en', ''),
'composite_strength': strength,
'asc_lagna': ha['asc_lagna'],
'moon_lagna': ha['moon_lagna'],
'sun_lagna': ha['sun_lagna'],
'verdict': verdict,
}
return areas
def generate_report(self, planet_lons: Dict, asc_lon: float, format: str = 'text') -> str:
"""
生成人类可读的 Sudarshana Chakra 报告。
Args:
planet_lons: {planet: sidereal_longitude}
asc_lon: sidereal ascendant longitude
format: 'text''json'
"""
charts = self.generate_three_charts(planet_lons, asc_lon)
composite = self.composite_analysis(planet_lons, asc_lon)
areas = self.life_area_analysis(planet_lons, asc_lon)
if format == 'json':
import json
return json.dumps({
'charts': charts,
'composite_analysis': composite,
'life_areas': areas,
}, ensure_ascii=False, indent=2, default=str)
# 文本报告
asc_idx = self._sign_idx_from_lon(asc_lon)
moon_idx = self._planet_sign_idx(planet_lons, 'Moon')
sun_idx = self._planet_sign_idx(planet_lons, 'Sun')
lines = []
lines.append("=" * 60)
lines.append("Sudarshana Chakra 三参考点盘分析")
lines.append("=" * 60)
lines.append("")
lines.append(f"参考点:")
lines.append(f" 上升 Lagna: {SIGNS[asc_idx]}({SIGNS_CN[SIGNS[asc_idx]]}) — 自我/身体")
lines.append(f" 月亮 Lagna: {SIGNS[moon_idx]}({SIGNS_CN[SIGNS[moon_idx]]}) — 情感/心理")
lines.append(f" 太阳 Lagna: {SIGNS[sun_idx]}({SIGNS_CN[SIGNS[sun_idx]]}) — 灵魂/生命力")
lines.append("")
# 三盘宫位一览
lines.append("-" * 60)
lines.append("行星在三盘中的宫位分布:")
lines.append(f"{'行星':10s} {'上升盘':>6s} {'月亮盘':>6s} {'太阳盘':>6s} {'吉宫数':>6s} {'评分':>6s} {'解读'}")
lines.append("-" * 60)
for planet in planet_lons:
if planet not in composite:
continue
c = composite[planet]
lines.append(
f"{planet:10s} {c['asc_house']:>6d} {c['moon_house']:>6d} {c['sun_house']:>6d} "
f"{c['favorable_count']:>6d} {c['composite_score']:>6.2f} {c['interpretation']}"
)
lines.append("")
# 12宫综合分析
lines.append("-" * 60)
lines.append("12宫生活领域综合评估:")
lines.append(f"{'宫位':>4s} {'领域':18s} {'强度':>6s} {'判定':>6s} {'上升盘主星':>10s} {'月亮盘主星':>10s} {'太阳盘主星':>10s}")
lines.append("-" * 60)
for area_name, data in areas.items():
h = data['house']
al_lord = data['asc_lagna']['lord'] + f"(H{data['asc_lagna']['lord_house'] or '?'})"
ml_lord = data['moon_lagna']['lord'] + f"(H{data['moon_lagna']['lord_house'] or '?'})"
sl_lord = data['sun_lagna']['lord'] + f"(H{data['sun_lagna']['lord_house'] or '?'})"
lines.append(
f"{h:>4d} {area_name:18s} {data['composite_strength']:>6.2f} {data['verdict']:>6s} "
f"{al_lord:>10s} {ml_lord:>10s} {sl_lord:>10s}"
)
lines.append("")
# 收敛性分析
lines.append("-" * 60)
lines.append("三盘收敛性分析 (行星在至少两盘中落入同一宫位):")
lines.append("-" * 60)
convergences = self._find_convergences_text(charts)
if convergences:
for c in convergences:
lines.append(f" {c}")
else:
lines.append(" 无显著收敛")
lines.append("")
# 总体评估
scores = [c['composite_score'] for c in composite.values() if isinstance(c.get('composite_score'), (int, float))]
avg_score = sum(scores) / len(scores) if scores else 0
strong = sum(1 for s in scores if s >= 0.67)
weak = sum(1 for s in scores if s <= 0.33)
lines.append("=" * 60)
lines.append("总体评估:")
lines.append(f" 平均复合评分: {avg_score:.2f}")
lines.append(f" 强势行星数(≥0.67): {strong}")
lines.append(f" 弱势行星数(≤0.33): {weak}")
if avg_score >= 0.6:
lines.append(" 总体判断: 盘面偏强,多数领域有支撑")
elif avg_score >= 0.4:
lines.append(" 总体判断: 盘面中等,需结合大运看时机")
else:
lines.append(" 总体判断: 盘面偏弱,需补救措施加强")
lines.append("=" * 60)
return "\n".join(lines)
def _find_convergences_text(self, charts: Dict) -> List[str]:
"""查找三盘收敛性,返回文本列表"""
results = []
for planet in self.seven_planets:
if planet not in charts.get('ascendant_lagna', {}):
continue
al_h = charts['ascendant_lagna'][planet]['house']
ml_h = charts['moon_lagna'][planet]['house']
sl_h = charts['sun_lagna'][planet]['house']
if al_h == ml_h == sl_h:
results.append(f"{planet}: 三盘同宫(H{al_h}) ★★★ 强收敛")
elif al_h == ml_h:
results.append(f"{planet}: 上升盘=月亮盘(H{al_h}) ★★ 中收敛")
elif al_h == sl_h:
results.append(f"{planet}: 上升盘=太阳盘(H{al_h}) ★★ 中收敛")
elif ml_h == sl_h:
results.append(f"{planet}: 月亮盘=太阳盘(H{ml_h}) ★★ 中收敛")
return results
# ============================================================================
# 便捷函数 — 供 jyotish_engine.py 调用
# ============================================================================
def calc_sudarshana_chakra(planet_lons: Dict, asc_lon: float,
house: int = None) -> Dict:
"""
计算完整的 Sudarshana Chakra 分析。
Args:
planet_lons: {planet_name: sidereal_longitude_0_360}
asc_lon: 上升点黄经 (sidereal, 0-360)
house: 可选,指定分析某宫位 (1-12)
Returns:
完整分析结果字典
"""
analyzer = SudarshanaChakraAnalyzer()
charts = analyzer.generate_three_charts(planet_lons, asc_lon)
composite = analyzer.composite_analysis(planet_lons, asc_lon)
areas = analyzer.life_area_analysis(planet_lons, asc_lon)
result = {
'method': 'Sudarshana Chakra 三参考点盘 (BPHS标准)',
'version': '2.0',
'reference_points': {
'ascendant_lagna': {
'sign': SIGNS[analyzer._sign_idx_from_lon(asc_lon)],
'sign_cn': SIGNS_CN[SIGNS[analyzer._sign_idx_from_lon(asc_lon)]],
'role': '自我/身体',
},
'moon_lagna': {
'sign': SIGNS[analyzer._planet_sign_idx(planet_lons, 'Moon')],
'sign_cn': SIGNS_CN[SIGNS[analyzer._planet_sign_idx(planet_lons, 'Moon')]],
'role': '情感/心理',
},
'sun_lagna': {
'sign': SIGNS[analyzer._planet_sign_idx(planet_lons, 'Sun')],
'sign_cn': SIGNS_CN[SIGNS[analyzer._planet_sign_idx(planet_lons, 'Sun')]],
'role': '灵魂/生命力',
},
},
'three_charts': charts,
'composite_analysis': composite,
'life_area_analysis': areas,
}
if house is not None:
result['specific_house'] = analyzer.house_analysis(planet_lons, asc_lon, house)
# 收敛性
convergences = []
for planet in analyzer.seven_planets:
if planet not in charts.get('ascendant_lagna', {}):
continue
al_h = charts['ascendant_lagna'][planet]['house']
ml_h = charts['moon_lagna'][planet]['house']
sl_h = charts['sun_lagna'][planet]['house']
if al_h == ml_h == sl_h:
convergences.append({
'planet': planet, 'house': al_h,
'level': 'triple', 'significance': 'high',
})
elif al_h == ml_h or al_h == sl_h or ml_h == sl_h:
match_h = al_h if al_h == ml_h or al_h == sl_h else ml_h
convergences.append({
'planet': planet, 'house': match_h,
'level': 'double', 'significance': 'medium',
})
result['convergence'] = {
'items': convergences,
'high_confidence': [c for c in convergences if c['significance'] == 'high'],
'medium_confidence': [c for c in convergences if c['significance'] == 'medium'],
}
# 总体评估
scores = [c['composite_score'] for c in composite.values()]
avg = sum(scores) / len(scores) if scores else 0
strong = sum(1 for s in scores if s >= 0.67)
weak = sum(1 for s in scores if s <= 0.33)
if avg >= 0.6:
overall = '盘面偏强,多数领域有支撑'
elif avg >= 0.4:
overall = '盘面中等,需结合大运看时机'
else:
overall = '盘面偏弱,需补救措施加强'
result['overall_assessment'] = {
'average_score': round(avg, 3),
'strong_planets': strong,
'weak_planets': weak,
'judgment': overall,
}
return result
def generate_sudarshana_report(planet_lons: Dict, asc_lon: float) -> str:
"""生成文本报告"""
analyzer = SudarshanaChakraAnalyzer()
return analyzer.generate_report(planet_lons, asc_lon, format='text')