Files
Jyotisha/scripts/kalachakra_dasha.py
2026-06-07 20:16:47 +08:00

311 lines
12 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 -*-
"""
kalachakra_dasha.py — Kalachakra Dasha System (Variable Cycle)
The Kalachakra Dasha is one of the most intricate Nakshatra-based dasha systems.
It is based on the Moon's Nakshatra and its Pada (quarter) at birth.
Key Concepts:
- 27 Nakshatras × 4 Padas = 108 Padas.
- Each Pada maps to a specific Rashi (Zodiac sign).
- Two directional modes:
* Savya (clockwise / forward)
* Apasavya (anti-clockwise / backward)
- 9 lords (Navatara-like) govern the periods in a fixed sequence.
Nakshatra Grouping (per standard texts):
Savya (forward) Nakshatras:
Ashwini, Bharani, Krittika, Ashlesha, Magha, Moola,
Purva Ashadha, Uttara Ashadha, Shravana, Dhanishta, Shatabhisha
Apasavya (backward) Nakshatras:
Rohini, Mrigashira, Ardra, Punarvasu, Pushya, Chitra,
Swati, Vishakha, Anuradha, Jyeshtha, Purva Bhadrapada,
Uttara Bhadrapada, Revati
(Note: Some authorities include Abhijit; this implementation uses the 27-Nakshatra scheme.)
9-Lord Sequence:
The 9 dasha lords correspond to the Navagraha sequence used in Kalachakra:
1. Ketu 2. Venus 3. Sun 4. Moon 5. Mars 6. Mercury 7. Jupiter 8. Saturn 9. Rahu
Their year allotments (one common traditional set):
Ketu: 7, Venus: 20, Sun: 6, Moon: 10, Mars: 7,
Mercury: 18, Jupiter: 16, Saturn: 19, Rahu: 17
Total: 7+20+6+10+7+18+16+19+17 = 120 years
However, Kalachakra is more commonly implemented with Rashi-based years.
This module uses a simplified Rashi-year model (see _RASHI_YEARS below).
Rashi Dasha Sequence and Years (simplified standard model):
Savya mode (forward through zodiac):
Capricorn 7 → Aquarius 8 → Pisces 9 → Aries 10 →
Taurus 11 → Gemini 12 → Cancer 13 → Leo 14 →
Virgo 15 → Libra 16 → Scorpio 17 → Sagittarius 18
Total = 150 years
Apasavya mode (backward through zodiac):
Cancer 13 → Gemini 12 → Taurus 11 → Aries 10 →
Pisces 9 → Aquarius 8 → Capricorn 7 → Sagittarius 18 →
Scorpio 17 → Libra 16 → Virgo 15 → Leo 14
Total = 150 years
Each of the 9 lords rules one consecutive Rashi in the sequence.
The starting point is determined by Moon's Nakshatra Pada.
Reference: Brihat Parashara Hora Shastra and various classical commentaries on Kalachakra.
"""
from datetime import datetime, timedelta
# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------
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"
]
RASHIS = [
"Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo",
"Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces"
]
# 9 Kalachakra lords in order
KALA_LORDS = [
"Ketu", "Venus", "Sun", "Moon", "Mars",
"Mercury", "Jupiter", "Saturn", "Rahu"
]
# Savya Rashi sequence (starting from Capricorn, forward)
SAVYA_RASHI_SEQ = [9, 10, 11, 0, 1, 2, 3, 4, 5, 6, 7, 8]
# Apasavya Rashi sequence (starting from Cancer, backward)
APASAVYA_RASHI_SEQ = [3, 2, 1, 0, 11, 10, 9, 8, 7, 6, 5, 4]
# Rashi year allotments (index 0-11)
# Savya: Cap 7, Aqua 8, Pis 9, Ari 10, Tau 11, Gem 12, Can 13, Leo 14, Vir 15, Lib 16, Sco 17, Sag 18
# Apasavya: Can 13, Gem 12, Tau 11, Ari 10, Pis 9, Aqua 8, Cap 7, Sag 18, Sco 17, Lib 16, Vir 15, Leo 14
_RASHI_YEARS_SAVYA = {
9: 7, 10: 8, 11: 9, 0: 10, 1: 11, 2: 12,
3: 13, 4: 14, 5: 15, 6: 16, 7: 17, 8: 18
}
_RASHI_YEARS_APASAVYA = {
3: 13, 2: 12, 1: 11, 0: 10, 11: 9, 10: 8,
9: 7, 8: 18, 7: 17, 6: 16, 5: 15, 4: 14
}
# Nakshatra → mode mapping (Savya = True, Apasavya = False)
# Per standard grouping
SAVYA_NAKSHATRAS = {0, 1, 2, 8, 9, 18, 19, 20, 21, 22, 23}
APASAVYA_NAKSHATRAS = {3, 4, 5, 6, 7, 13, 14, 15, 16, 17, 24, 25, 26}
# Pada → starting Rashi offset within the sequence (simplified model)
# For Savya: Pada 1 → Cap(0), Pada 2 → Aqu(1), Pada 3 → Pis(2), Pada 4 → Ari(3)
# For Apasavya: Pada 1 → Can(0), Pada 2 → Gem(1), Pada 3 → Tau(2), Pada 4 → Ari(3)
# The actual classical mapping is more nuanced; this is a practical approximation.
PADA_OFFSET_MAP = {1: 0, 2: 1, 3: 2, 4: 3}
# ---------------------------------------------------------------------------
# Helper Functions
# ---------------------------------------------------------------------------
def _get_mode(nakshatra_index: int) -> str:
"""Return 'savya' or 'apasavya' based on Nakshatra."""
if nakshatra_index in SAVYA_NAKSHATRAS:
return "savya"
elif nakshatra_index in APASAVYA_NAKSHATRAS:
return "apasavya"
else:
# Default fallback (should not happen with valid 0-26 input)
return "savya"
def _build_major_periods(mode: str, start_offset: int, birth_date: datetime) -> list:
"""
Build major periods for Kalachakra Dasha.
Args:
mode: 'savya' or 'apasavya'.
start_offset: offset into the 12-sign Rashi sequence.
birth_date: datetime of birth.
Returns:
list of period dicts.
"""
if mode == "savya":
rashi_seq = SAVYA_RASHI_SEQ
years_map = _RASHI_YEARS_SAVYA
else:
rashi_seq = APASAVYA_RASHI_SEQ
years_map = _RASHI_YEARS_APASAVYA
periods = []
current_date = birth_date
seq_len = len(rashi_seq)
for i in range(seq_len):
seq_idx = (start_offset + i) % seq_len
rashi_idx = rashi_seq[seq_idx]
lord_idx = i % 9 # 9 lords cycle through 12 rashis
lord = KALA_LORDS[lord_idx]
years = years_map[rashi_idx]
end_date = current_date + timedelta(days=years * 365.25)
periods.append({
"lord": lord,
"rashi": RASHIS[rashi_idx],
"rashi_index": rashi_idx,
"years": years,
"start_date": current_date.isoformat(),
"end_date": end_date.isoformat(),
})
current_date = end_date
return periods
# ---------------------------------------------------------------------------
# Core Functions
# ---------------------------------------------------------------------------
def calculate_kalachakra_dasha(birth_info: dict) -> dict:
"""
Calculate Kalachakra Dasha for a native.
Args:
birth_info: dict containing:
- "moon_nakshatra_index": int (0-26)
- "moon_pada": int (1-4)
- "birth_datetime": datetime object or ISO-format string (optional)
Returns:
dict with keys:
- "mode": str — 'savya' or 'apasavya'
- "major": list of major period dicts
- "current": dict — the currently running major period
- "total_cycle": int — sum of years in one full cycle
- "starting_lord": str
- "starting_rashi": str
"""
nakshatra_idx = birth_info.get("moon_nakshatra_index", 0)
pada = birth_info.get("moon_pada", 1)
birth_dt = birth_info.get("birth_datetime")
if isinstance(birth_dt, str):
birth_dt = datetime.fromisoformat(birth_dt)
if birth_dt is None:
birth_dt = datetime.now()
# Clamp pada to 1-4
if not 1 <= pada <= 4:
pada = ((pada - 1) % 4) + 1
mode = _get_mode(nakshatra_idx)
start_offset = PADA_OFFSET_MAP.get(pada, 0)
major_periods = _build_major_periods(mode, start_offset, birth_dt)
total_cycle = sum(p["years"] for p in major_periods)
starting_lord = major_periods[0]["lord"] if major_periods else None
starting_rashi = major_periods[0]["rashi"] if major_periods else None
# Determine current period. Kalachakra cycles through the generated Rashi-year
# sequence repeatedly; older natives should still return a current period.
now = datetime.now()
current_period = None
age_years = max((now - birth_dt).days / 365.25, 0)
current_in_cycle = age_years % total_cycle if total_cycle else age_years
cumulative = 0.0
for p in major_periods:
years = p["years"]
if cumulative <= current_in_cycle < cumulative + years:
cycle_start = birth_dt + timedelta(days=(age_years - current_in_cycle + cumulative) * 365.25)
cycle_end = cycle_start + timedelta(days=years * 365.25)
current_period = p.copy()
current_period["start_date"] = cycle_start.isoformat()
current_period["end_date"] = cycle_end.isoformat()
current_period["elapsed_years"] = (now - cycle_start).days / 365.25
current_period["remaining_years"] = (cycle_end - now).days / 365.25
current_period["cycle_number"] = int(age_years // total_cycle) + 1 if total_cycle else 1
break
cumulative += years
return {
"mode": mode,
"major": major_periods,
"current": current_period,
"total_cycle": total_cycle,
"starting_lord": starting_lord,
"starting_rashi": starting_rashi,
}
# ---------------------------------------------------------------------------
# Self-test
# ---------------------------------------------------------------------------
if __name__ == "__main__":
# Example 1: Savya mode — Moon in Ashwini (Savya), Pada 1
test_birth_1 = {
"moon_nakshatra_index": 0, # Ashwini (Savya)
"moon_pada": 1,
"birth_datetime": datetime(1990, 6, 15, 10, 30),
}
result_1 = calculate_kalachakra_dasha(test_birth_1)
print("=" * 70)
print("Test 1: Moon in Ashwini (Savya), Pada 1")
print("=" * 70)
print(f"Mode : {result_1['mode']}")
print(f"Starting Lord : {result_1['starting_lord']}")
print(f"Starting Rashi : {result_1['starting_rashi']}")
print(f"Total Cycle : {result_1['total_cycle']} years")
print("Major Periods:")
for p in result_1["major"]:
print(f" {p['lord']:8s} | {p['rashi']:12s} | {p['years']:2d} years | {p['start_date'][:10]}{p['end_date'][:10]}")
print(f"Current Period : {result_1['current']}")
print()
# Example 2: Apasavya mode — Moon in Rohini (Apasavya), Pada 2
test_birth_2 = {
"moon_nakshatra_index": 3, # Rohini (Apasavya)
"moon_pada": 2,
"birth_datetime": datetime(1985, 3, 20, 8, 0),
}
result_2 = calculate_kalachakra_dasha(test_birth_2)
print("=" * 70)
print("Test 2: Moon in Rohini (Apasavya), Pada 2")
print("=" * 70)
print(f"Mode : {result_2['mode']}")
print(f"Starting Lord : {result_2['starting_lord']}")
print(f"Starting Rashi : {result_2['starting_rashi']}")
print(f"Total Cycle : {result_2['total_cycle']} years")
for p in result_2["major"]:
print(f" {p['lord']:8s} | {p['rashi']:12s} | {p['years']:2d} years | {p['start_date'][:10]}{p['end_date'][:10]}")
print(f"Current Period : {result_2['current']}")
print()
# Example 3: Savya mode — Moon in Shravana (Savya), Pada 4
test_birth_3 = {
"moon_nakshatra_index": 21, # Shravana (Savya)
"moon_pada": 4,
"birth_datetime": datetime(2000, 1, 1, 0, 0),
}
result_3 = calculate_kalachakra_dasha(test_birth_3)
print("=" * 70)
print("Test 3: Moon in Shravana (Savya), Pada 4")
print("=" * 70)
print(f"Mode : {result_3['mode']}")
print(f"Starting Lord : {result_3['starting_lord']}")
print(f"Starting Rashi : {result_3['starting_rashi']}")
print(f"Total Cycle : {result_3['total_cycle']} years")
for p in result_3["major"]:
print(f" {p['lord']:8s} | {p['rashi']:12s} | {p['years']:2d} years | {p['start_date'][:10]}{p['end_date'][:10]}")