Files
Jyotisha/scripts/shadbala_advanced.py
T
2026-06-24 13:37:33 +08:00

442 lines
14 KiB
Python
Raw 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 -*-
"""
Shadbala 高级模块 v1.0 — Kala Bala完整子项 + Yuddha Bala + Sputa Drishti
MIT License — 参考jyotishganit(northtara)实现适配pyswisseph
新增功能:
1. Varsha Bala(年力量)— 基于太阳进入春分点的星期主星
2. Maasa Bala(月力量)— 基于太阳进入当月的星期主星
3. Dina Bala(日力量)— 基于出生日星期主星
4. Hora Bala(时力量)— 基于出生时的Hora主星
5. Yuddha Bala(交战力量)— 行星交战时的力量调整
6. Sputa DrishtiSputa相位)— BPHS标准的相位强度计算
"""
import math
import swisseph as swe
from typing import Dict, List, Tuple
from datetime import datetime, timedelta
# ============================================================================
# 常量(BPHS标准)
# ============================================================================
WEEKDAY_LORDS = ["Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn"]
# 吠陀行星小时序列(基于Kalapurusha
PLANETARY_HOUR_SEQUENCE = [0, 5, 3, 1, 6, 4, 2]
# 索引对应WEEKDAY_LORDS: 0=Sun, 5=Venus, 3=Mercury, 1=Moon, 6=Saturn, 4=Jupiter, 2=Mars
# Tribhaga 三段主星
TRIBHAGA_DAY_LORDS = ["Sun", "Mercury", "Saturn"]
TRIBHAGA_NIGHT_LORDS = ["Moon", "Venus", "Mars"]
# Yuddha Bala可交战行星
YUDDHABALA_PLANETS = ["Mars", "Mercury", "Jupiter", "Venus", "Saturn"]
# 行星直径(用于Yuddha Bala交战计算)
PLANET_DIAMETERS = {
"Mars": 1.5, "Mercury": 1.0, "Jupiter": 3.5,
"Venus": 1.6, "Saturn": 3.0,
}
# Sputa Drishti 相位权重(0-360度分段)
# Sputa相位公式:drik_value = (longitude_diff - 180) * aspect_weight / 180
# 火星额外关注4/8宫,木星额外关注5/9宫,土星额外关注3/10宫
# ============================================================================
# 1. 辅助:太阳节气进入计算(基于pyswisseph)
# ============================================================================
def _get_solar_ingress_datetime(target_lon: float, year: int) -> datetime:
"""
计算太阳进入指定黄道经度的UTC时间
Args:
target_lon: 目标黄道经度(0-360)
year: 年份
Returns:
datetime: 节气进入的UTC时间
"""
# 使用swe.solcross计算太阳穿越指定经度的时间
# 从该年1月1日0时开始搜索
start_jd = swe.julday(year, 1, 1, 0)
# solcross返回太阳第一次穿越该经度的Julian Day
# 注意:对于0°(春分点),可以直接计算
t_jd = swe.solcross(target_lon, start_jd, 0)
# 转换为datetime
year_i, month_i, day_i, hour_f = swe.revjul(t_jd)
hour = int(hour_f)
minute = int((hour_f - hour) * 60)
second = int(((hour_f - hour) * 60 - minute) * 60)
return datetime(year_i, month_i, day_i, hour, minute, second)
def _get_vedic_weekday(dt: datetime) -> int:
"""
获取吠陀星期几(0=Sunday, 1=Monday, ..., 6=Saturday
吠陀星期从日出开始计算,简单的近似:
对于印度,可以认为每天从日出(约6AM)开始
如果在日出前,归为前一天
"""
# 一天从日出开始(简化为6:00
if dt.hour < 6:
dt = dt - timedelta(days=1)
# Python weekday: 0=Monday, ..., 6=Sunday
python_wd = dt.weekday()
# 转换:Python Monday(0) → Vedic Monday(1), Python Sunday(6) → Vedic Sunday(0)
vedic_wd = (python_wd + 1) % 7
return vedic_wd
def get_varsha_lord(year: int) -> str:
"""
获取Varsha Lord(年主星)
基于太阳进入白羊座0°时的星期主星
"""
ingress_dt = _get_solar_ingress_datetime(0.0, year)
vedic_wd = _get_vedic_weekday(ingress_dt)
return WEEKDAY_LORDS[vedic_wd]
def get_maasa_lord(solar_lon: float, year: int) -> str:
"""
获取Maasa Lord(月主星)
基于太阳进入当前星座时的星期主星
Args:
solar_lon: 太阳当前黄道经度
year: 年份
"""
sign_start = int(solar_lon / 30) * 30.0
ingress_dt = _get_solar_ingress_datetime(sign_start, year)
vedic_wd = _get_vedic_weekday(ingress_dt)
return WEEKDAY_LORDS[vedic_wd]
def get_vaara_lord(year: int, month: int, day: int, hour: float) -> str:
"""
获取Vaara Lord(日主星)
基于出生日星期
Args:
hour: 出生时间的小时(0-24),用于判断是否在日出前
"""
dt = datetime(year, month, day, int(hour))
vedic_wd = _get_vedic_weekday(dt)
return WEEKDAY_LORDS[vedic_wd]
def get_hora_lord(year: int, month: int, day: int, hour: float, lat: float, lon: float, tz: float) -> str:
"""
获取Hora Lord(时主星)
基于日出后的Hora序列
规则:
- 日出日落之间:白天Hora,从日主星开始
- 日落日出之间:夜晚Hora,从日主星+5开始
"""
# 当前增强层只作为证据补充;保持与主 shadbala.py 一致的轻量近似。
sunrise_hour = 6.0
sunset_hour = 18.0
# 拿到日主星索引
dt = datetime(year, month, day, int(hour))
vedic_wd = _get_vedic_weekday(dt)
if sunrise_hour <= hour < sunset_hour:
# 白天:从日主星开始
start_idx = vedic_wd
else:
# 夜晚:从日主星+5开始(第6个Hora主星)
start_idx = (vedic_wd + 5) % 7
hours_since_sunrise = (hour - sunrise_hour + 24) % 24
current_hora = int(hours_since_sunrise) % 7
ora_wd = PLANETARY_HOUR_SEQUENCE[(start_idx + current_hora) % 7]
return WEEKDAY_LORDS[PLANETARY_HOUR_SEQUENCE[ora_wd]]
# ============================================================================
# 2. Kala Bala 完整子项(新增Varsha/Maasa/Dina/Hora
# ============================================================================
def calc_varsha_maasa_dina_hora_bala(pname: str, year: int, month: int, day: int,
hour: float, solar_lon: float,
lat: float = 0, lon: float = 0, tz: float = 0) -> float:
"""
计算Varsha/Maasa/Dina/Hora四项综合得分(max 150
- Varsha Bala (年): 15分
- Maasa Bala (月): 30分
- Dina Bala (日): 45分
- Hora Bala (时): 60分
总分 = 15+30+45+60 = 150 Virupas
"""
bala = 0.0
# Varsha Lord
varsha_lord = get_varsha_lord(year)
if pname == varsha_lord:
bala += 15.0
# Maasa Lord
try:
maasa_lord = get_maasa_lord(solar_lon, year)
if pname == maasa_lord:
bala += 30.0
except Exception as e:
import logging
logging.warning(f"[shadbala] maasa lord calculation failed: {e}")
# Dina Lord
vaara_lord = get_vaara_lord(year, month, day, hour)
if pname == vaara_lord:
bala += 45.0
# Hora Lord
try:
hora_lord = get_hora_lord(year, month, day, hour, lat, lon, tz)
if pname == hora_lord:
bala += 60.0
except Exception as e:
import logging
logging.warning(f"[shadbala] hora lord calculation failed: {e}")
return bala
# ============================================================================
# 3. Yuddha Bala(交战力量)
# ============================================================================
def calc_yuddha_bala(planet_data: Dict[str, Dict]) -> Dict[str, float]:
"""
Yuddha Bala 计算 — 行星交战时的力量调整
规则(BPHS + jyotishganit实现):
- 当两颗行星度差 <= 1°时发生交战
- 交战:Shadbala总分高的行星获胜,获得+调整值
- 战败的行星获得-调整值
- 调整值 = bala_diff / diameter_diff
- 仅限Mars/Mercury/Jupiter/Venus/Saturn参与
Args:
planet_data: {pname: {"shadbala_shadbala": total_shadbala, "degree": float, ...}}
Returns:
{pname: yuddha_bala_value} 正=胜,负=败,0=未参战
"""
results = {p: 0.0 for p in YUDDHABALA_PLANETS}
planets_list = [p for p in YUDDHABALA_PLANETS if p in planet_data]
for i in range(len(planets_list)):
for j in range(i + 1, len(planets_list)):
p1 = planets_list[i]
p2 = planets_list[j]
lon1 = planet_data[p1].get("degree", 0)
lon2 = planet_data[p2].get("degree", 0)
# 计算度差
diff = abs(lon1 - lon2)
if diff > 180:
diff = 360 - diff
# 只有<=1°才是交战
if diff > 1.0:
continue
# 双方Shadbala总分
bala1 = planet_data[p1].get("shadbala_total", 0)
bala2 = planet_data[p2].get("shadbala_total", 0)
if bala1 == bala2:
continue
winner, loser = (p1, p2) if bala1 > bala2 else (p2, p1)
# 调整值 = 力量差 / 直径差
bala_diff = abs(bala1 - bala2)
dia1 = PLANET_DIAMETERS.get(p1, 1.0)
dia2 = PLANET_DIAMETERS.get(p2, 1.0)
dia_diff = abs(dia1 - dia2)
if dia_diff > 0.01:
yuddha = round(bala_diff / dia_diff, 2)
else:
yuddha = bala_diff
results[winner] = yuddha
results[loser] = -yuddha
return results
# ============================================================================
# 4. Sputa DrishtiSputa相位计算)
# ============================================================================
def calc_sputa_drishti(aspecting_lon: float, aspected_lon: float) -> float:
"""
Sputa Drishti 强度计算
BPHS标准:相位强度基于经度差计算
0-60°: 1/4强度
60-120°: 3/4强度(三合相位)
120-180°: 1/2强度
180-240°: 1强度(对冲相位)
240-300°: 1/4强度
300-360°: 1/2强度
火星额外看60°(4宫取余)和120°(8宫取余)
木星额外看120°(5宫取余)和240°(9宫取余)
土星额外看90°(3宫取余)和300°(10宫取余)
Args:
aspecting_lon: 施相位行星的黄道经度
aspected_lon: 受相位行星的黄道经度
Returns:
相位强度值(0-60 Virupas
"""
diff = (aspected_lon - aspecting_lon + 360) % 360
raw_diff = diff if diff <= 180 else 360 - diff
# BPHS相位强度分段
if diff == 0 or diff == 360:
intensity = 0
elif diff <= 60:
intensity = raw_diff / 60.0 * 15.0
elif diff <= 120:
intensity = (raw_diff - 60) / 60.0 * 30.0 + 15.0
elif diff <= 180:
intensity = (raw_diff - 120) / 60.0 * 15.0 + 45.0
elif diff <= 240:
intensity = (360 - diff) / 60.0 * 30.0 + 15.0
elif diff <= 300:
intensity = (360 - diff) / 60.0 * 15.0
else:
intensity = (360 - diff) / 60.0 * 15.0
return round(intensity, 2)
def get_sputa_drishti_matrix(planet_data: Dict[str, Dict]) -> Dict[str, Dict[str, float]]:
"""
计算完整的Sputa Drishti矩阵
Returns:
{planet_a: {planet_b: drishti_strength, ...}, ...}
"""
planets = [p for p in ["Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn"]
if p in planet_data]
matrix = {}
for p in planets:
lon_p = planet_data[p].get("degree", 0)
matrix[p] = {}
for q in planets:
if p == q:
continue
lon_q = planet_data[q].get("degree", 0)
# 基本相位
drishti = calc_sputa_drishti(lon_p, lon_q)
# 行星特殊相位:7宫=180°是普适的
# 火星特殊相位已经在calc_sputa_drishti中通过分段体现
# jyotishganit的完整Sputa还会考虑Vakra(逆行)修正
matrix[p][q] = drishti
return matrix
def calc_drik_bala_sputa(pname: str, all_planets: Dict[str, Dict],
benefic_list: List[str] = None,
malefic_list: List[str] = None) -> float:
"""
基于Sputa Drishti的Drik Bala计算
公式:
Drik Bala = (Benefic_Sputa - Malefic_Sputa) / 4
Args:
pname: 目标行星
all_planets: {pname: {"degree": float, ...}}
benefic_list: 吉星列表(默认Jupiter/Venus/Mercury
malefic_list: 凶星列表(默认Saturn/Mars/Sun
Returns:
Drik Bala值(-60 到 +60 Virupas
"""
if benefic_list is None:
benefic_list = ["Jupiter", "Venus", "Mercury"]
if malefic_list is None:
malefic_list = ["Saturn", "Mars", "Sun"]
matrix = get_sputa_drishti_matrix(all_planets)
benefic_sum = 0.0
malefic_sum = 0.0
for q, strength in matrix.get(pname, {}).items():
if q in benefic_list:
benefic_sum += strength
elif q in malefic_list:
malefic_sum += strength
drik = (benefic_sum - malefic_sum) / 4.0
return max(-60.0, min(60.0, drik))
# ============================================================================
# 5. 完整高级Shadbala集成(替换原有calc_shadbala中的计算)
# ============================================================================
def upgrade_kala_bala(original_kala: Dict, pname: str,
year: int, month: int, day: int, hour: float,
solar_lon: float, lat: float = 0, lon: float = 0,
tz: float = 0) -> Dict:
"""
在原Kala Bala基础上添加Varsha/Maasa/Dina/Hora子项
Args:
original_kala: 原calc_kala_bala的返回值
pname: 行星名称
Returns:
升级后的Kala Bala字典(含+4子项)
"""
result = dict(original_kala)
# 计算Varsha/Maasa/Dina/Hora
vmdh = calc_varsha_maasa_dina_hora_bala(
pname, year, month, day, hour, solar_lon, lat, lon, tz
)
result["varsha_maasa_dina_hora_bala"] = vmdh
result["varsha_bala"] = 15.0 if vmdh >= 150 else (vmdh % 15 if vmdh > 0 else 0)
result["total"] = (result.get("total", 0) + vmdh)
return result
__all__ = [
"get_varsha_lord", "get_maasa_lord", "get_vaara_lord", "get_hora_lord",
"calc_varsha_maasa_dina_hora_bala",
"calc_yuddha_bala",
"calc_sputa_drishti", "get_sputa_drishti_matrix", "calc_drik_bala_sputa",
"upgrade_kala_bala",
]