Enhance Jyotish precision techniques and tests
This commit is contained in:
+492
-116
@@ -1,138 +1,514 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Bhava Chalit 宫位系统 v1.0
|
||||
MIT License — 基于 dashaflow (adarshj322) 的等宫制实现
|
||||
Bhava Chalit (不等宫边界调整) 计算模块 v1.0
|
||||
|
||||
Bhava Chalit = 等分宫制,从上升中点(Lagna - 15°)开始,每宫30度
|
||||
与整宫制(Whole Sign)的区别:靠近星座边界的行星可能跨宫
|
||||
Bhava Chalit 是 JHora 和 PyJHora 的标准功能。它根据实际宫位边界
|
||||
(非等宫30°)调整行星的宫位归属。
|
||||
|
||||
核心概念:
|
||||
- Bhava Madhya: 宫位中点(宫头/cusp)
|
||||
- Bhava Sandhi: 宫位边界(相邻两宫头的中点)
|
||||
- 行星根据落在哪两个 Sandhi 边界之间来确定 Bhava 宫位
|
||||
- Bhava 宫位可能与 Rashi(星座/整宫)宫位不同
|
||||
|
||||
支持的宫位制:
|
||||
- equal: 等宫制(每宫30°,从上升点起算)
|
||||
- whole_sign: 整宫制(星座=宫位)
|
||||
- sripati: Sripati(Porphyry 变体,吠陀标准)
|
||||
- porphyry: Porphyry(四象限三等分)
|
||||
- placidus: Placidus(时间等分,西方最常用)
|
||||
- koch: Koch(时间等分,西方流行)
|
||||
"""
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
SIGNS = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo',
|
||||
'Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces']
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
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': '双鱼座'}
|
||||
|
||||
|
||||
def calculate_bhava_chalit(asc_lon: float, raw_planets: Dict) -> Dict:
|
||||
"""
|
||||
Bhava Chalit 计算(等宫制从上升中点划分)
|
||||
|
||||
规则:
|
||||
- 第1宫中点(Bhava Madhya) = Lagna 经度
|
||||
- 第1宫起点 = Lagna - 15°
|
||||
- 每宫跨度 = 30°
|
||||
- 行星的Bhava宫位可能与其Rashi(整宫)宫位不同
|
||||
|
||||
Args:
|
||||
asc_lon: 上升点黄道经度 (0-360)
|
||||
raw_planets: {planet_name: {"lon": float, ...}}
|
||||
每颗行星需要lon(黄道经度)和sign_idx(星座索引)
|
||||
|
||||
Returns:
|
||||
dict: {planet_name: {"bhava_house": int, "rashi_house": int, "shifted": bool}}
|
||||
"""
|
||||
cusp_start = (asc_lon - 15.0) % 360.0
|
||||
asc_sign_idx = int(asc_lon / 30) % 12
|
||||
def _norm(lon: float) -> float:
|
||||
"""归一化到 [0, 360)"""
|
||||
return lon % 360.0
|
||||
|
||||
result = {}
|
||||
for name, rp in raw_planets.items():
|
||||
planet_lon = rp.get("lon", 0)
|
||||
|
||||
# 整宫制
|
||||
sign_idx = rp.get("sign_idx", int(planet_lon / 30) % 12)
|
||||
rashi_house = ((sign_idx - asc_sign_idx) % 12) + 1
|
||||
|
||||
# Bhava 宫位(等宫制)
|
||||
diff = (planet_lon - cusp_start) % 360.0
|
||||
bhava_house = int(diff / 30.0) + 1
|
||||
if bhava_house > 12:
|
||||
bhava_house = 12
|
||||
|
||||
result[name] = {
|
||||
"bhava_house": bhava_house,
|
||||
"rashi_house": rashi_house,
|
||||
"shifted": bhava_house != rashi_house,
|
||||
def _sign_idx(lon: float) -> int:
|
||||
"""黄经对应的星座索引 (0-11)"""
|
||||
return int(_norm(lon) / 30) % 12
|
||||
|
||||
|
||||
def _angular_dist(a: float, b: float) -> float:
|
||||
"""从 a 到 b 的正向角距离 [0, 360)"""
|
||||
return _norm(b - a)
|
||||
|
||||
|
||||
class BhavaChalitCalculator:
|
||||
"""Bhava Chalit (不等宫边界调整) 计算器。"""
|
||||
|
||||
HOUSE_SYSTEMS = {
|
||||
'equal': 'Equal house (30° each)',
|
||||
'placidus': 'Placidus (time-based, most common Western)',
|
||||
'porphyry': 'Porphyry (quadrant trisection)',
|
||||
'sripati': 'Sripati (Vedic standard, Porphyry variant)',
|
||||
'whole_sign': 'Whole Sign (Rashi = House)',
|
||||
'koch': 'Koch (time-based, popular in Western)',
|
||||
}
|
||||
|
||||
# swisseph house system codes
|
||||
_SWE_HSYS = {
|
||||
'placidus': b'P',
|
||||
'koch': b'K',
|
||||
'porphyry': b'O',
|
||||
'sripati': b'R', # Sripati uses Regiomontanus approximation in swe
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self._has_swe = False
|
||||
try:
|
||||
import swisseph as swe
|
||||
self._has_swe = True
|
||||
self._swe = swe
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 核心算法: 宫头计算
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def calculate_cusps(self, asc_lon: float, mc_lon: float,
|
||||
house_system: str = 'sripati',
|
||||
jd: float = None, lat: float = None,
|
||||
lon: float = None) -> List[float]:
|
||||
"""计算12个宫头(Bhava Madhya)。
|
||||
|
||||
Parameters
|
||||
----------
|
||||
asc_lon : float 上升点黄经 (sidereal)
|
||||
mc_lon : float 天顶黄经 (sidereal), 仅 sripati/porphyry 需要
|
||||
house_system : str 宫位制
|
||||
jd : float 儒略日, swisseph 宫位制需要
|
||||
lat : float 纬度, swisseph 宫位制需要
|
||||
lon : float 经度, swisseph 宫位制需要
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[float] 12个宫头黄经, 索引0=第1宫, 索引1=第2宫, ...
|
||||
"""
|
||||
hs = house_system.lower()
|
||||
if hs not in self.HOUSE_SYSTEMS:
|
||||
raise ValueError(f"不支持的宫位制: {house_system}。"
|
||||
f"可选: {list(self.HOUSE_SYSTEMS.keys())}")
|
||||
|
||||
if hs == 'equal':
|
||||
return self._cusps_equal(asc_lon)
|
||||
elif hs == 'whole_sign':
|
||||
return self._cusps_whole_sign(asc_lon)
|
||||
elif hs == 'sripati':
|
||||
return self._cusps_sripati(asc_lon, mc_lon)
|
||||
elif hs == 'porphyry':
|
||||
return self._cusps_porphyry(asc_lon, mc_lon)
|
||||
elif hs in ('placidus', 'koch'):
|
||||
return self._cusps_swe(asc_lon, hs, jd, lat, lon)
|
||||
else:
|
||||
return self._cusps_equal(asc_lon)
|
||||
|
||||
def _cusps_equal(self, asc_lon: float) -> List[float]:
|
||||
"""等宫制: 每宫30°, 从上升点起算。"""
|
||||
return [_norm(asc_lon + i * 30) for i in range(12)]
|
||||
|
||||
def _cusps_whole_sign(self, asc_lon: float) -> List[float]:
|
||||
"""整宫制: 每宫=一个星座, 宫头在星座中点 (Bhava Madhya)。
|
||||
|
||||
在 Jyotish 中, 宫头 (cusp) 是 Bhava Madhya (宫位中点)。
|
||||
Whole Sign 下, 中点在 15° of each sign。
|
||||
Sandhi (边界) 在星座0°, 确保不会出现 Rashi/Bhava 偏移。
|
||||
"""
|
||||
asc_sign_start = int(asc_lon / 30) * 30
|
||||
# cusp = midpoint of each sign = sign_start + 15°
|
||||
return [_norm(asc_sign_start + i * 30 + 15) for i in range(12)]
|
||||
|
||||
def _cusps_porphyry(self, asc_lon: float, mc_lon: float) -> List[float]:
|
||||
"""Porphyry: 四象限三等分。
|
||||
|
||||
四个象限:
|
||||
Q1: Asc → MC (顺时针, 即 MC 在 Asc 之前/之上)
|
||||
Q2: MC → Desc (7宫 = Asc+180°)
|
||||
Q3: Desc → IC (4宫 = MC+180°)
|
||||
Q4: IC → Asc
|
||||
每个象限三等分 → 每象限3个宫。
|
||||
"""
|
||||
desc_lon = _norm(asc_lon + 180)
|
||||
ic_lon = _norm(mc_lon + 180)
|
||||
cusps = [0.0] * 12
|
||||
|
||||
# 1宫 = Asc, 10宫 = MC, 7宫 = Desc, 4宫 = IC
|
||||
cusps[0] = _norm(asc_lon)
|
||||
cusps[9] = _norm(mc_lon)
|
||||
cusps[6] = _norm(desc_lon)
|
||||
cusps[3] = _norm(ic_lon)
|
||||
|
||||
# Q1: Asc → MC (houses 12, 11, 10-cusp)
|
||||
# 在黄道上, MC 通常在 Asc 的顺时针方向 (数值上 MC < Asc 或绕过360)
|
||||
# 行星沿黄道逆时针运行, 但宫位顺时针排列
|
||||
# Q1 从 MC 到 Asc (顺时针) 包含 house 11, 12
|
||||
# 但实际上 Porphyry 的象限划分是:
|
||||
# Q1 (houses 10,11,12): MC → Asc
|
||||
# Q2 (houses 7,8,9): Desc → MC
|
||||
# Q3 (houses 4,5,6): IC → Desc
|
||||
# Q4 (houses 1,2,3): Asc → IC
|
||||
# 注意: 在印度占星中, 宫位顺时针, 2宫在1宫之后
|
||||
|
||||
# 正确的象限划分 (JHora/Porphyry 标准):
|
||||
# Q1: Asc → IC (houses 2, 3) — 1宫和4宫之间
|
||||
# Q2: IC → Desc (houses 5, 6) — 4宫和7宫之间
|
||||
# Q3: Desc → MC (houses 8, 9) — 7宫和10宫之间
|
||||
# Q4: MC → Asc (houses 11, 12) — 10宫和1宫之间
|
||||
|
||||
self._trisect_quadrant(cusps, 0, 3, 1, 2) # Asc → IC: houses 2,3
|
||||
self._trisect_quadrant(cusps, 3, 6, 4, 5) # IC → Desc: houses 5,6
|
||||
self._trisect_quadrant(cusps, 6, 9, 7, 8) # Desc → MC: houses 8,9
|
||||
self._trisect_quadrant(cusps, 9, 0, 10, 11) # MC → Asc: houses 11,12
|
||||
|
||||
return cusps
|
||||
|
||||
def _cusps_sripati(self, asc_lon: float, mc_lon: float) -> List[float]:
|
||||
"""Sripati: Porphyry 变体, 吠陀标准。
|
||||
|
||||
与 Porphyry 相同的四象限三等分法。
|
||||
Sripati 的特点是: 先用 Porphyry 算出宫头,
|
||||
然后每个宫的中点 (midpoint between cusps) 才是真正的 Bhava Madhya。
|
||||
但在 JHora 的实现中, Sripati 宫头就是 Porphyry 宫头,
|
||||
差异仅在于 Sandhi (边界) 的计算方式。
|
||||
|
||||
这里采用与 JHora 一致的 Sripati 算法:
|
||||
即 Porphyry 宫头 + Sandhi 在相邻 Porphyry 宫头中点。
|
||||
"""
|
||||
return self._cusps_porphyry(asc_lon, mc_lon)
|
||||
|
||||
def _trisect_quadrant(self, cusps: List[float],
|
||||
start_idx: int, end_idx: int,
|
||||
inner1: int, inner2: int):
|
||||
"""将象限三等分, 填入两个内部宫头。
|
||||
|
||||
从 cusps[start_idx] 到 cusps[end_idx], 顺时针方向。
|
||||
"""
|
||||
start_lon = cusps[start_idx]
|
||||
end_lon = cusps[end_idx]
|
||||
arc = _angular_dist(start_lon, end_lon)
|
||||
third = arc / 3.0
|
||||
cusps[inner1] = _norm(start_lon + third)
|
||||
cusps[inner2] = _norm(start_lon + 2 * third)
|
||||
|
||||
def _cusps_swe(self, asc_lon: float, house_system: str,
|
||||
jd: float, lat: float, lon: float) -> List[float]:
|
||||
"""使用 swisseph 计算宫头 (Placidus/Koch 等)。
|
||||
|
||||
swisseph houses/houses_ex 返回12个值 (0-indexed):
|
||||
cusps[0]=H1, cusps[1]=H2, ..., cusps[11]=H12
|
||||
这些是 tropical 度数, 需要减去 ayanamsa 转为 sidereal。
|
||||
"""
|
||||
if not self._has_swe:
|
||||
raise RuntimeError(f"swisseph 未安装, 无法使用 {house_system} 宫位制")
|
||||
if jd is None or lat is None or lon is None:
|
||||
raise ValueError(f"{house_system} 需要 jd, lat, lon 参数")
|
||||
|
||||
hsys = self._SWE_HSYS.get(house_system, b'P')
|
||||
cusps_swe, ascmc = self._swe.houses(jd, lat, lon, hsys)
|
||||
|
||||
ayanamsa = self._swe.get_ayanamsa(jd)
|
||||
result = []
|
||||
for i in range(12):
|
||||
result.append(_norm(cusps_swe[i] - ayanamsa))
|
||||
return result
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 核心: Bhava Sandhi (宫位边界)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def calculate_sandhis(self, cusps: List[float]) -> List[float]:
|
||||
"""计算12个 Bhava Sandhi (宫位边界)。
|
||||
|
||||
Sandhi[i] = 宫i的起始边界 = 从 cusp[i-1] 到 cusp[i] 的中点
|
||||
即相邻两宫头的中点 (沿黄道正向)。
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[float] 12个Sandhi, sandhi[0]=第1宫起始边界, ...
|
||||
"""
|
||||
sandhis = []
|
||||
for i in range(12):
|
||||
prev_cusp = cusps[(i - 1) % 12]
|
||||
curr_cusp = cusps[i]
|
||||
# 从 prev_cusp 沿黄道正向到 curr_cusp 的中点
|
||||
mid = _norm(prev_cusp + _angular_dist(prev_cusp, curr_cusp) / 2.0)
|
||||
sandhis.append(mid)
|
||||
return sandhis
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 核心: 行星 Bhava 归属
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _planet_bhava(self, planet_lon: float, sandhis: List[float]) -> int:
|
||||
"""确定行星落在哪个 Bhava。
|
||||
|
||||
行星落在 sandhi[i] 和 sandhi[(i+1)%12] 之间 → 第(i+1)宫。
|
||||
|
||||
Returns
|
||||
-------
|
||||
int 宫位 (1-12)
|
||||
"""
|
||||
for i in range(12):
|
||||
start = sandhis[i]
|
||||
end = sandhis[(i + 1) % 12]
|
||||
arc = _angular_dist(start, end)
|
||||
pos = _angular_dist(start, planet_lon)
|
||||
if pos < arc:
|
||||
return i + 1
|
||||
# fallback: 最近的宫
|
||||
return 1
|
||||
|
||||
def _planet_rashi_house(self, planet_lon: float, asc_lon: float) -> int:
|
||||
"""整宫制宫位 (Rashi house)。"""
|
||||
p_si = _sign_idx(planet_lon)
|
||||
a_si = _sign_idx(asc_lon)
|
||||
return ((p_si - a_si) % 12) + 1
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 公共 API
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def calculate_bhava_boundaries(self, asc_lon: float, mc_lon: float,
|
||||
house_system: str = 'sripati',
|
||||
jd: float = None, lat: float = None,
|
||||
lon: float = None) -> Dict:
|
||||
"""计算完整的宫位边界信息。
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict with keys:
|
||||
house_system, cusps, sandhis, houses_detail
|
||||
"""
|
||||
cusps = self.calculate_cusps(asc_lon, mc_lon, house_system,
|
||||
jd, lat, lon)
|
||||
sandhis = self.calculate_sandhis(cusps)
|
||||
|
||||
houses_detail = []
|
||||
for i in range(12):
|
||||
start = sandhis[i]
|
||||
end = sandhis[(i + 1) % 12]
|
||||
span = _angular_dist(start, end)
|
||||
mid = cusps[i]
|
||||
mid_sign = SIGNS[_sign_idx(mid)]
|
||||
detail = {
|
||||
'house': i + 1,
|
||||
'cusp_lon': round(mid, 4),
|
||||
'cusp_sign': mid_sign,
|
||||
'cusp_sign_cn': SIGNS_CN[mid_sign],
|
||||
'cusp_degree_in_sign': round(mid - _sign_idx(mid) * 30, 4),
|
||||
'sandhi_start_lon': round(start, 4),
|
||||
'sandhi_end_lon': round(end, 4),
|
||||
'span_degrees': round(span, 4),
|
||||
}
|
||||
houses_detail.append(detail)
|
||||
|
||||
return {
|
||||
'house_system': house_system,
|
||||
'house_system_desc': self.HOUSE_SYSTEMS.get(house_system, ''),
|
||||
'ascendant_lon': round(asc_lon, 4),
|
||||
'mc_lon': round(mc_lon, 4),
|
||||
'cusps': [round(c, 4) for c in cusps],
|
||||
'sandhis': [round(s, 4) for s in sandhis],
|
||||
'houses': houses_detail,
|
||||
}
|
||||
|
||||
return result
|
||||
def get_bhava_chalit_chart(self, planet_lons: Dict[str, float],
|
||||
asc_lon: float, mc_lon: float,
|
||||
house_system: str = 'sripati',
|
||||
jd: float = None, lat: float = None,
|
||||
lon: float = None) -> Dict:
|
||||
"""根据 Bhava 边界重新分配行星宫位。
|
||||
|
||||
Parameters
|
||||
----------
|
||||
planet_lons : dict {行星名: sidereal黄经}
|
||||
asc_lon : float 上升点黄经
|
||||
mc_lon : float 天顶黄经
|
||||
house_system: str 宫位制
|
||||
jd, lat, lon swisseph 宫位制所需参数
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict with keys:
|
||||
house_system, boundaries, planets
|
||||
"""
|
||||
cusps = self.calculate_cusps(asc_lon, mc_lon, house_system,
|
||||
jd, lat, lon)
|
||||
sandhis = self.calculate_sandhis(cusps)
|
||||
|
||||
planets = {}
|
||||
for pname, plon in planet_lons.items():
|
||||
bhava = self._planet_bhava(plon, sandhis)
|
||||
rashi = self._planet_rashi_house(plon, asc_lon)
|
||||
si = _sign_idx(plon)
|
||||
deg_in_sign = plon - si * 30
|
||||
|
||||
planets[pname] = {
|
||||
'longitude': round(plon, 4),
|
||||
'sign': SIGNS[si],
|
||||
'sign_cn': SIGNS_CN[SIGNS[si]],
|
||||
'degree_in_sign': round(deg_in_sign, 4),
|
||||
'rashi_house': rashi,
|
||||
'bhava_house': bhava,
|
||||
'shifted': bhava != rashi,
|
||||
'shift_direction': 'forward' if bhava > rashi or (bhava == 1 and rashi == 12)
|
||||
else 'backward' if bhava != rashi
|
||||
else 'none',
|
||||
}
|
||||
# 修正 shift_direction: 考虑环绕
|
||||
if bhava != rashi:
|
||||
diff = ((bhava - rashi) % 12)
|
||||
if diff <= 6:
|
||||
planets[pname]['shift_direction'] = 'forward'
|
||||
else:
|
||||
planets[pname]['shift_direction'] = 'backward'
|
||||
|
||||
return {
|
||||
'house_system': house_system,
|
||||
'house_system_desc': self.HOUSE_SYSTEMS.get(house_system, ''),
|
||||
'ascendant_lon': round(asc_lon, 4),
|
||||
'mc_lon': round(mc_lon, 4),
|
||||
'planets': planets,
|
||||
'shifted_planets': [p for p, d in planets.items() if d['shifted']],
|
||||
'summary': {
|
||||
'total_planets': len(planets),
|
||||
'shifted_count': sum(1 for d in planets.values() if d['shifted']),
|
||||
'shifted_names': [p for p, d in planets.items() if d['shifted']],
|
||||
}
|
||||
}
|
||||
|
||||
def compare_rashi_vs_bhava(self, planet_lons: Dict[str, float],
|
||||
asc_lon: float, mc_lon: float,
|
||||
house_system: str = 'sripati',
|
||||
jd: float = None, lat: float = None,
|
||||
lon: float = None) -> Dict:
|
||||
"""对比 Rashi (整宫) vs Bhava Chalit 宫位, 显示偏移。
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict with keys:
|
||||
house_system, rashi_chart, bhava_chart, shifts, boundaries
|
||||
"""
|
||||
# Rashi chart (whole sign)
|
||||
rashi_chart = {}
|
||||
asc_si = _sign_idx(asc_lon)
|
||||
for pname, plon in planet_lons.items():
|
||||
si = _sign_idx(plon)
|
||||
house = ((si - asc_si) % 12) + 1
|
||||
rashi_chart[pname] = {
|
||||
'sign': SIGNS[si],
|
||||
'house': house,
|
||||
'degree_in_sign': round(plon - si * 30, 4),
|
||||
}
|
||||
|
||||
# Bhava Chalit chart
|
||||
bhava_result = self.get_bhava_chalit_chart(
|
||||
planet_lons, asc_lon, mc_lon, house_system, jd, lat, lon)
|
||||
|
||||
# Shifts
|
||||
shifts = []
|
||||
for pname in planet_lons:
|
||||
rh = rashi_chart[pname]['house']
|
||||
bh = bhava_result['planets'][pname]['bhava_house']
|
||||
if rh != bh:
|
||||
diff = ((bh - rh) % 12)
|
||||
direction = 'forward' if diff <= 6 else 'backward'
|
||||
magnitude = min(diff, 12 - diff)
|
||||
shifts.append({
|
||||
'planet': pname,
|
||||
'sign': rashi_chart[pname]['sign'],
|
||||
'degree_in_sign': rashi_chart[pname]['degree_in_sign'],
|
||||
'rashi_house': rh,
|
||||
'bhava_house': bh,
|
||||
'shift_direction': direction,
|
||||
'shift_magnitude': magnitude,
|
||||
'note': f"{pname} 从第{rh}宫偏移到第{bh}宫 ({direction})"
|
||||
})
|
||||
|
||||
# Boundaries
|
||||
boundaries = self.calculate_bhava_boundaries(
|
||||
asc_lon, mc_lon, house_system, jd, lat, lon)
|
||||
|
||||
return {
|
||||
'house_system': house_system,
|
||||
'house_system_desc': self.HOUSE_SYSTEMS.get(house_system, ''),
|
||||
'ascendant_lon': round(asc_lon, 4),
|
||||
'mc_lon': round(mc_lon, 4),
|
||||
'rashi_chart': rashi_chart,
|
||||
'bhava_chart': {p: d['bhava_house']
|
||||
for p, d in bhava_result['planets'].items()},
|
||||
'shifts': shifts,
|
||||
'shifted_count': len(shifts),
|
||||
'boundaries': boundaries,
|
||||
}
|
||||
|
||||
|
||||
def get_bhava_cusps(asc_lon: float) -> List[float]:
|
||||
"""
|
||||
获取所有12宫的Bhava Cusp(宫头)经度
|
||||
|
||||
Returns:
|
||||
12个float,第1宫到第12宫的Cusp经度
|
||||
"""
|
||||
cusp_start = (asc_lon - 15.0) % 360.0
|
||||
return [(cusp_start + i * 30.0) % 360.0 for i in range(12)]
|
||||
# ======================================================================
|
||||
# CLI 入口
|
||||
# ======================================================================
|
||||
|
||||
def cmd_bhava_chalit(args):
|
||||
"""bhava-chalit 子命令处理函数。"""
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
|
||||
def get_bhava_madhyas(asc_lon: float) -> List[float]:
|
||||
"""
|
||||
获取所有12宫的Bhava Madhya(宫中点)经度
|
||||
|
||||
Returns:
|
||||
12个float,第1宫到第12宫的中点经度
|
||||
"""
|
||||
return [(asc_lon + i * 30.0) % 360.0 for i in range(12)]
|
||||
# 延迟导入, 避免循环依赖
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from jyotish_engine import compute_chart_data, HAS_SWE, output_json
|
||||
|
||||
if not HAS_SWE:
|
||||
return {"error": "swisseph 未安装, 无法计算"}
|
||||
|
||||
def get_bhava_ranges(asc_lon: float) -> List[Tuple[float, float]]:
|
||||
"""
|
||||
获取每个宫位的起止范围
|
||||
|
||||
Returns:
|
||||
12个tuple (start, end),第1宫到第12宫的经度范围
|
||||
"""
|
||||
cusps = get_bhava_cusps(asc_lon)
|
||||
ranges = []
|
||||
for i in range(12):
|
||||
start = cusps[i]
|
||||
end = cusps[(i + 1) % 12]
|
||||
if end <= start:
|
||||
end += 360
|
||||
ranges.append((start, end))
|
||||
return ranges
|
||||
chart, asc_idx, jd, ayanamsa = compute_chart_data(
|
||||
args.year, args.month, args.day, args.hour, args.minute,
|
||||
args.lat, args.lon, args.tz, getattr(args, 'node_mode', 'mean'))
|
||||
|
||||
if chart is None:
|
||||
return {"error": "星盘计算失败"}
|
||||
|
||||
def planet_in_which_bhava(planet_lon: float, asc_lon: float) -> int:
|
||||
"""
|
||||
判断行星经度落在哪个Bhava宫(1-12)
|
||||
"""
|
||||
cusps = get_bhava_cusps(asc_lon)
|
||||
for i in range(12):
|
||||
start = cusps[i]
|
||||
end = cusps[(i + 1) % 12]
|
||||
if end <= start:
|
||||
end += 360
|
||||
plon = planet_lon if planet_lon >= start else planet_lon + 360
|
||||
if start <= plon < end:
|
||||
return i + 1
|
||||
return 12
|
||||
# 提取行星黄经
|
||||
planet_lons = {}
|
||||
for pname, pdata in chart.get('planets', {}).items():
|
||||
if 'degree_raw' in pdata:
|
||||
planet_lons[pname] = pdata['degree_raw']
|
||||
|
||||
# 上升点和MC黄经
|
||||
asc_lon = chart['ascendant']['degree_raw']
|
||||
# MC: 从 swisseph 重新获取
|
||||
import swisseph as swe
|
||||
hour_decimal = args.hour + args.minute / 60.0 - args.tz
|
||||
jd_val = swe.julday(args.year, args.month, args.day, hour_decimal)
|
||||
cusps_raw, ascmc = swe.houses(jd_val, args.lat, args.lon, b'A')
|
||||
mc_tropical = ascmc[1] # MC
|
||||
mc_lon = (mc_tropical - ayanamsa) % 360
|
||||
|
||||
def cross_house_check(asc_lon: float, planet_name: str, planet_lon: float,
|
||||
planet_sign_idx: int) -> Dict:
|
||||
"""
|
||||
跨宫检查:判断行星是否因Bhava Chalit而换宫
|
||||
|
||||
Returns:
|
||||
{"planet": str, "rashi_house": int, "bhava_house": int,
|
||||
"shifted": bool, "delta_degrees": float}
|
||||
"""
|
||||
asc_sign_idx = int(asc_lon / 30) % 12
|
||||
rashi_house = ((planet_sign_idx - asc_sign_idx) % 12) + 1
|
||||
bhava_house = planet_in_which_bhava(planet_lon, asc_lon)
|
||||
|
||||
delta = abs(bhava_house - rashi_house)
|
||||
if delta > 6:
|
||||
delta = 12 - delta
|
||||
|
||||
return {
|
||||
"planet": planet_name,
|
||||
"rashi_house": rashi_house,
|
||||
"bhava_house": bhava_house,
|
||||
"shifted": bhava_house != rashi_house,
|
||||
"delta_houses": delta,
|
||||
}
|
||||
house_system = getattr(args, 'house_system', 'sripati')
|
||||
calc = BhavaChalitCalculator()
|
||||
|
||||
mode = getattr(args, 'mode', 'compare')
|
||||
|
||||
if mode == 'boundaries':
|
||||
return calc.calculate_bhava_boundaries(
|
||||
asc_lon, mc_lon, house_system, jd_val, args.lat, args.lon)
|
||||
elif mode == 'chart':
|
||||
return calc.get_bhava_chalit_chart(
|
||||
planet_lons, asc_lon, mc_lon, house_system,
|
||||
jd_val, args.lat, args.lon)
|
||||
else: # compare
|
||||
return calc.compare_rashi_vs_bhava(
|
||||
planet_lons, asc_lon, mc_lon, house_system,
|
||||
jd_val, args.lat, args.lon)
|
||||
|
||||
Reference in New Issue
Block a user