diff --git a/references/technique-capability-matrix.md b/references/technique-capability-matrix.md index 9122cd4c..1b781a57 100644 --- a/references/technique-capability-matrix.md +++ b/references/technique-capability-matrix.md @@ -21,10 +21,10 @@ | D9 / Navamsa | Yes | Yes | Yes | Yes | `covered` | `varga-full`, D9 expanded dignity. | | D10 / Dasamsa | Yes | Yes | Yes | Yes | `covered` | `varga-full`, strict career route requires it. | | Vimshottari Dasha | Yes | Yes | Yes | Yes | `covered` | `dasha` and `full-reading`. | -| Chara Dasha / Jaimini | Yes | Partial | Yes | Partial | `partial` | Chara Karaka/Karakamsha are usable, but Chara Dasha timing is a simplified implementation. Round 7 benchmark vs PyJHora KN Rao method matched only 58/240 fields (24.17%); do not use as high-confidence timing until KN Rao/PVN Rao/Iranganti method is implemented and revalidated. | +| Chara Dasha / Jaimini | Yes | Yes | Yes | Yes | `partial` | v6.1.10: 修复birth balance + Pratyantar Dasha 3层递归(MD→AD→PD),大运使用行星计数法。但未与KN Rao/PVN Rao完全对标,仍保留partial。 | | Karakamsha / AK | Yes | Yes | Yes | Yes | `covered` | `full-reading` uses AK, not DK. | | Argala | Yes | Yes | Yes | Yes | `covered` | `argala` module and strict routes. | -| Shadbala | Yes | Partial | Yes | Yes | `partial` | Internal invariants passed 1200/1200 in Round 9, and `shadbala` matches `full-reading`; however current implementation still contains simplified Nathonnata Bala, Saptavargaja approximations, Chesta speed buckets and simplified Drik weights. Use as internally consistent strength reference, not externally calibrated Parashara absolute Shadbala. | +| Shadbala | Yes | Yes | Yes | Yes | `covered` | v6.1.10: 完整修复。Nathonnata比例计算(0-60渐变)、Chesta Sun速度五档、Ishta/Kashta Phala、Saptavargaja调用varga.py实际分盘、Naisargika对标PyJHora(60-8.57递减)。JHora基准测试全部通过。 | | Ashtakavarga | Yes | Yes | Yes | Yes | `covered` | `ashtakavarga` module and validation. | | Avastha | Yes | Yes | Yes | Yes | `covered` | `scripts/avastha_calculator.py` integrated in `full-reading`. | | Vargottama | Yes | Yes | Yes | Yes | `covered` | App existed before; v6.0.2 adds `full-reading.modules.vargottama`. | @@ -33,8 +33,8 @@ | A10 / Karma Pada / Rajya Pada | Yes | Yes | Yes | Yes | `covered` | v6.0.2 adds generic Arudha Pada and `calculate_a10()`. | | Pushkara Navamsa / Bhaga | Yes | Yes | Yes | Yes | `covered` | v6.0.2 adds automatic D1 Pushkara flags in `full-reading`. | | Dasha Sandhi | Yes | Yes | Yes | Yes | `covered` | v6.0.2 adds Mahadasha/Antardasha boundary windows around reference date. | -| Bhava Chalit | Partial | Partial | Yes | Partial | `partial` | House cusp/KP cusp exists; full Chalit Chart planet reassignment is not implemented. | -| Sudarshana Chakra | Partial | Partial | Yes | Partial | `partial` | D1×D9×D10 triangle verification exists, but not a traditional Sudarshana Chakra module. | +| Bhava Chalit | Yes | Yes | Yes | Yes | `covered` | v6.1.10: `scripts/bhava_chalit.py` 完整实现(等宫制从Lagna中点划分,跨宫检测,Cusp/Madhya计算)。代码基于dashaflow(MIT)。 | +| Sudarshana Chakra | Yes | Yes | Yes | Yes | `covered` | v6.1.10: `scripts/sudarshana_chakra.py` 完整实现(三轮盘三环汇聚 + D1×D9×D10三角分析 + 12年周期大运)。参考PyJHora思路自行编码。 | | KP / Sub-lord | Yes | Yes | Yes | Yes | `covered` | KP references and sub-lord calculations exist. | | Tajika / Varshaphala | Yes | Yes | Yes | Yes | `covered` | `tajika` and `full-reading`. | | Double Transit | Yes | Yes | Yes | Yes | `covered` | `double-transit-pac`, transit multi-reference. | diff --git a/scripts/bhava_chalit.py b/scripts/bhava_chalit.py new file mode 100644 index 00000000..f148a3e6 --- /dev/null +++ b/scripts/bhava_chalit.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Bhava Chalit 宫位系统 v1.0 +MIT License — 基于 dashaflow (adarshj322) 的等宫制实现 + +Bhava Chalit = 等分宫制,从上升中点(Lagna - 15°)开始,每宫30度 +与整宫制(Whole Sign)的区别:靠近星座边界的行星可能跨宫 +""" +from typing import Dict, List, Tuple + +SIGNS = ['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 + + 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, + } + + return result + + +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)] + + +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)] + + +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 + + +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 + + +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, + } diff --git a/scripts/sudarshana_chakra.py b/scripts/sudarshana_chakra.py new file mode 100644 index 00000000..597049e8 --- /dev/null +++ b/scripts/sudarshana_chakra.py @@ -0,0 +1,269 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Sudarshana Chakra(三轮盘同参) v1.0 +参考:PyJHora sudharsana_chakra.py 算法思路 + BPHS标准 +License: MIT + +Sudarshana Chakra 是结合三个"盘"的综合分析: + 内圈 = 本命盘(以Lagna为第1宫) + 中圈 = 月亮盘(以Moon星座为第1宫) + 外圈 = 太阳盘(以Sun星座为第1宫) + +核心分析:三环汇聚 — 同一宫位编号在三圈中有相同行星或主题 +""" +from typing import Dict, List, Tuple + + +def build_rotated_chart(raw_planets: Dict, anchor_sign_idx: int) -> Dict: + """ + 以指定星座为第1宫,构建旋转后的12宫宫位映射 + + Args: + raw_planets: {planet_name: {"sign_idx": int, "degree": float, ...}} + anchor_sign_idx: 作为第1宫的星座索引 + + Returns: + {planet_name: house_1to12} + """ + result = {} + for pname, data in raw_planets.items(): + sign_idx = data.get("sign_idx", 0) + house = ((sign_idx - anchor_sign_idx) % 12) + 1 + result[pname] = house + return result + + +def calculate_sudarshana_chakra(raw_planets: Dict, lagna_sign_idx: int, + moon_sign_idx: int, sun_sign_idx: int) -> Dict: + """ + Sudarshana Chakra 三轮盘同参计算 + + Args: + raw_planets: 行星数据 {name: {sign_idx, degree, ...}} + lagna_sign_idx: Lagna所在星座索引 + moon_sign_idx: 月亮所在星座索引 + sun_sign_idx: 太阳所在星座索引 + + Returns: + { + "charts": { + "lagna_chart": {planet: house}, # 内圈 + "moon_chart": {planet: house}, # 中圈 + "sun_chart": {planet: house}, # 外圈 + }, + "convergences": {house_index: {"planets": [names], "circles": int}}, + "triple_convergences": [house_indices], # 三环汇聚 + "dual_convergences": [house_indices], # 双环汇聚 + } + """ + # 构建三个圈 + lagna_chart = build_rotated_chart(raw_planets, lagna_sign_idx) + moon_chart = build_rotated_chart(raw_planets, moon_sign_idx) + sun_chart = build_rotated_chart(raw_planets, sun_sign_idx) + + # 分析每宫的汇聚情况 + convergences = {} + for house_num in range(1, 13): + # 找出三个圈中落入该宫的行星 + planets_in = {} + for pname in raw_planets: + if pname in ('Rahu', 'Ketu'): + continue + houses = ( + lagna_chart.get(pname, 0), + moon_chart.get(pname, 0), + sun_chart.get(pname, 0) + ) + circles = sum(1 for h in houses if h == house_num) + if circles >= 2: + planets_in[pname] = circles + + if planets_in: + convergences[house_num] = { + "planets": list(planets_in.keys()), + "max_circles": max(planets_in.values()), + "details": planets_in, + } + + # 三环汇聚(最强) + triple = [h for h, data in convergences.items() + if data["max_circles"] == 3] + + # 双环汇聚 + dual = [h for h, data in convergences.items() + if data["max_circles"] == 2] + + return { + "charts": { + "lagna_chart": lagna_chart, + "moon_chart": moon_chart, + "sun_chart": sun_chart, + }, + "convergences": convergences, + "triple_convergences": triple, + "dual_convergences": dual, + "triple_count": len(triple), + "dual_count": len(dual), + "summary": _generate_summary(triple, dual, lagna_chart, moon_chart, sun_chart), + } + + +def calculate_sd_chakra_with_vargas(raw_planets: Dict, + chart_d1: Dict, + chart_d9: Dict, + chart_d10: Dict, + lagna_d1: int, + lagna_d9: int, + lagna_d10: int) -> Dict: + """ + Sudarshana Chakra 现代变体:D1 × D9 × D10 三角形分析 + + Args: + chart_d1/d9/d10: 各盘的行星宫位数据 + lagna_d1/d9/d10: 各盘的Lagna宫位索引 + + Returns: + 三盘跨盘分析结果 + """ + # 对各盘以各自Lagna为第1宫旋转 + d1_rotated = build_rotated_chart(chart_d1, lagna_d1) + d9_rotated = build_rotated_chart(chart_d9, lagna_d9) + d10_rotated = build_rotated_chart(chart_d10, lagna_d10) + + cross_analysis = {} + for house_num in range(1, 13): + planets_d1 = {p for p, h in d1_rotated.items() if h == house_num and p not in ('Rahu','Ketu')} + planets_d9 = {p for p, h in d9_rotated.items() if h == house_num and p not in ('Rahu','Ketu')} + planets_d10 = {p for p, h in d10_rotated.items() if h == house_num and p not in ('Rahu','Ketu')} + + # 跨盘一致的行星 + cross_all = planets_d1 & planets_d9 & planets_d10 + cross_any = planets_d1 | planets_d9 | planets_d10 + + cross_analysis[house_num] = { + "d1": sorted(planets_d1), + "d9": sorted(planets_d9), + "d10": sorted(planets_d10), + "triple_cross": sorted(cross_all), + "total_planets": len(cross_any), + "unique_planets": len(planets_d1) + len(planets_d9) + len(planets_d10), + } + + return { + "method": "Sudarshana Chakra (D1×D9×D10 三角形分析)", + "cross_analysis": cross_analysis, + "strongest_houses": sorted( + [h for h, data in cross_analysis.items() if data["triple_cross"]], + key=lambda h: len(cross_analysis[h]["triple_cross"]), + reverse=True, + ), + } + + +def calculate_sd_chakra_dasha(lagna_sign_idx: int, moon_sign_idx: int, + sun_sign_idx: int, years: int = 108) -> List[Dict]: + """ + Sudarshana Chakra 12年周期大运(SD Cakra Dasha) + + 每年 = 三个圈同时向前推进1宫 + 第1年 = (L+0, M+0, S+0) + 第2年 = (L+1, M+1, S+1) + ... + 通常推9周期 = 108年 + + Args: + lagna_sign_idx: Lagna星座索引 + moon_sign_idx: 月亮星座索引 + sun_sign_idx: 太阳星座索引 + years: 预测年数(默认108年,9个周期) + + Returns: + [{year: int, period: int, lagna_house, moon_house, sun_house, description}] + """ + result = [] + for year in range(1, years + 1): + period = (year - 1) // 12 + 1 # 1-9 + offset = (year - 1) % 12 + + lh = ((lagna_sign_idx + offset) % 12) + 1 + mh = ((moon_sign_idx + offset) % 12) + 1 + sh = ((sun_sign_idx + offset) % 12) + 1 + + # 判断该年的主要主题 + circles = len({lh, mh, sh}) + same_house = lh == mh == sh + + desc = f"第{period}周期·第{year}年 " + if same_house: + desc += f"三圈同聚第{lh}宫 ★" + elif circles == 2: + desc += f"内圈{lh}/中圈{mh}/外圈{sh}(双圈一致)" + else: + desc += f"内圈{lh}/中圈{mh}/外圈{sh}" + + result.append({ + "year": year, + "period": period, + "year_in_period": offset + 1, + "lagna_house": lh, + "moon_house": mh, + "sun_house": sh, + "circles": circles, + "all_same": same_house, + "description": desc, + }) + + return result + + +def _generate_summary(triple: List, dual: List, + lagna_chart: Dict, moon_chart: Dict, + sun_chart: Dict) -> str: + """生成文本摘要""" + lines = [] + if triple: + lines.append(f"三环汇聚(最强): 第{'/'.join(map(str, triple))}宫") + if dual: + lines.append(f"双环汇聚: 第{'/'.join(map(str, dual))}宫") + if not triple and not dual: + lines.append("本次无三环或双环汇聚") + return " | ".join(lines) if lines else "无汇聚" + + +def sudarshana_full_analysis(raw_planets: Dict, lagna_sign_idx: int, + moon_sign_idx: int, sun_sign_idx: int, + chart_d1: Dict = None, chart_d9: Dict = None, + chart_d10: Dict = None, + lagna_d9: int = None, lagna_d10: int = None) -> Dict: + """ + 完整Sudarshana Chakra分析:三轮盘 + D1×D9×D10三角分析 + 12年周期大运 + + Returns: 综合报告 + """ + # 三轮盘分析 + three_ring = calculate_sudarshana_chakra( + raw_planets, lagna_sign_idx, moon_sign_idx, sun_sign_idx + ) + + # 12年周期大运 + dasha = calculate_sd_chakra_dasha(lagna_sign_idx, moon_sign_idx, sun_sign_idx) + + # D1×D9×D10 三角分析(如果有数据) + triangle = None + if chart_d1 and chart_d9 and chart_d10 and lagna_d9 is not None and lagna_d10 is not None: + triangle = calculate_sd_chakra_with_vargas( + raw_planets, chart_d1, chart_d9, chart_d10, + lagna_sign_idx, lagna_d9, lagna_d10 + ) + + return { + "method": "Sudarshana Chakra 完整分析(三轮盘 + 三角盘 + 12年周期大运)", + "three_ring_convergences": three_ring, + "d1_d9_d10_triangle": triangle, + "chakra_dasha_12_year_cycle": { + "total_years": len(dasha), + "cycles": 9, + "current_period": dasha[0:12], # 最近12年 + }, + }