TECHNICAL DEEP-DIVE

Jyotish Calculations Explained: A Technical Deep-Dive

Master the mathematics behind Vedic astrology. Complete guide to planetary calculations, ayanamsa, divisional charts, dasha systems, and house computations.

December 23, 2025 20 min read

Introduction to Jyotish Mathematics

Vedic astrology (Jyotish) is fundamentally a mathematical science. Unlike Western psychological astrology, Jyotish relies on precise astronomical calculations. This guide explains the core formulas, algorithms, and mathematical principles that power every Vedic astrology application.

Prerequisites for Understanding

  • Astronomy Basics: Celestial sphere, ecliptic, precession
  • Coordinate Systems: Ecliptic longitude/latitude
  • Time Systems: UTC, Julian Day, sidereal time
  • Mathematics: Trigonometry, modular arithmetic

Part 1: Julian Day Calculation

All astronomical calculations start with converting dates to Julian Day Number (JD) - a continuous count of days since January 1, 4713 BCE.

Julian Day Formula

def date_to_julian_day(year, month, day, hour, minute, second):
    """
    Convert Gregorian calendar date to Julian Day Number

    Args:
        year: Year (e.g., 2025)
        month: Month (1-12)
        day: Day of month (1-31)
        hour: Hour (0-23)
        minute: Minute (0-59)
        second: Second (0-59)

    Returns:
        Julian Day as float
    """
    # Adjust for January and February
    if month <= 2:
        year -= 1
        month += 12

    # Calculate Julian Day at midnight
    A = int(year / 100)
    B = 2 - A + int(A / 4)

    JD = int(365.25 * (year + 4716)) + \
         int(30.6001 * (month + 1)) + \
         day + B - 1524.5

    # Add time component
    day_fraction = (hour + minute/60 + second/3600) / 24
    JD += day_fraction

    return JD

# Example: December 23, 2025, 14:30:00
jd = date_to_julian_day(2025, 12, 23, 14, 30, 0)
print(f"Julian Day: {jd}")  # 2460666.104166667

Why Julian Day Matters

Swiss Ephemeris and all astronomical libraries use JD as input. It eliminates calendar complexities (leap years, varying month lengths) and provides a linear time scale for calculations.

Part 2: Planetary Positions

Calculating accurate planetary positions is the foundation of Jyotish. We use Swiss Ephemeris for highest accuracy.

Using Swiss Ephemeris

import swisseph as swe

# Set ephemeris files path
swe.set_ephe_path('/usr/share/ephe')

def get_planet_position(jd, planet_id, tropical=True):
    """
    Get planetary position using Swiss Ephemeris

    Args:
        jd: Julian Day
        planet_id: Planet constant (swe.SUN, swe.MOON, etc.)
        tropical: True for tropical, False for sidereal

    Returns:
        Dictionary with position data
    """
    # Calculate position
    # Flag: SEFLG_SWIEPH (Swiss Ephemeris) + SEFLG_SPEED
    flags = swe.FLG_SWIEPH | swe.FLG_SPEED

    result, ret_flag = swe.calc_ut(jd, planet_id, flags)

    longitude = result[0]  # Ecliptic longitude (0-360°)
    latitude = result[1]   # Ecliptic latitude
    distance = result[2]   # Distance in AU
    speed = result[3]      # Daily motion in degrees

    return {
        'longitude': longitude,
        'latitude': latitude,
        'distance': distance,
        'speed': speed,
        'retrograde': speed < 0
    }

# Calculate Sun position
jd = date_to_julian_day(2025, 12, 23, 14, 30, 0)
sun_pos = get_planet_position(jd, swe.SUN)

print(f"Sun Longitude: {sun_pos['longitude']:.4f}°")
print(f"Sun Speed: {sun_pos['speed']:.4f}°/day")
print(f"Retrograde: {sun_pos['retrograde']}")

All Planets and Points

# Planet constants in Swiss Ephemeris
PLANETS = {
    'Sun': swe.SUN,           # 0
    'Moon': swe.MOON,         # 1
    'Mercury': swe.MERCURY,   # 2
    'Venus': swe.VENUS,       # 3
    'Mars': swe.MARS,         # 4
    'Jupiter': swe.JUPITER,   # 5
    'Saturn': swe.SATURN,     # 6
    'Rahu': swe.MEAN_NODE,    # 10 (Mean North Node)
    'Ketu': swe.MEAN_NODE,    # 10 (Ketu = Rahu + 180°)
    'Uranus': swe.URANUS,     # 7
    'Neptune': swe.NEPTUNE,   # 8
    'Pluto': swe.PLUTO        # 9
}

def get_all_planets(jd):
    """Get positions of all planets"""
    positions = {}

    for name, planet_id in PLANETS.items():
        pos = get_planet_position(jd, planet_id)

        # Special handling for Ketu (opposite of Rahu)
        if name == 'Ketu':
            pos['longitude'] = (pos['longitude'] + 180) % 360

        positions[name] = pos

    return positions

Part 3: Ayanamsa Calculation

Ayanamsa converts tropical (Western) positions to sidereal (Vedic). It represents the precession of equinoxes - Earth's 26,000-year wobble.

Popular Ayanamsas

Lahiri (Chitrapaksha): Most popular in India - 24°09' (2025)
Raman: Popular in South India - 21°39' (2025)
Krishnamurti: KP system - 23°52' (2025)
True Chitrapaksha: Based on Spica star observation

Implementing Ayanamsa

def convert_to_sidereal(tropical_longitude, jd, ayanamsa='lahiri'):
    """
    Convert tropical longitude to sidereal

    Args:
        tropical_longitude: Tropical ecliptic longitude (0-360°)
        jd: Julian Day
        ayanamsa: 'lahiri', 'raman', 'krishnamurti', etc.

    Returns:
        Sidereal longitude (0-360°)
    """
    # Set ayanamsa mode
    ayanamsa_modes = {
        'lahiri': swe.SIDM_LAHIRI,
        'raman': swe.SIDM_RAMAN,
        'krishnamurti': swe.SIDM_KRISHNAMURTI,
        'yukteshwar': swe.SIDM_YUKTESHWAR,
        'jn_bhasin': swe.SIDM_JN_BHASIN
    }

    swe.set_sid_mode(ayanamsa_modes.get(ayanamsa, swe.SIDM_LAHIRI))

    # Get ayanamsa value for this Julian Day
    ayanamsa_value = swe.get_ayanamsa_ut(jd)

    # Convert to sidereal
    sidereal_longitude = (tropical_longitude - ayanamsa_value) % 360

    return sidereal_longitude, ayanamsa_value

# Example
tropical_sun = 271.5436  # Sun at ~271° tropical
jd = date_to_julian_day(2025, 12, 23, 14, 30, 0)

sidereal_sun, ayanamsa = convert_to_sidereal(tropical_sun, jd, 'lahiri')

print(f"Tropical: {tropical_sun:.4f}°")
print(f"Ayanamsa: {ayanamsa:.4f}°")
print(f"Sidereal: {sidereal_sun:.4f}°")

Understanding Zodiac Signs

def longitude_to_sign(longitude):
    """
    Convert longitude to zodiac sign

    Args:
        longitude: Ecliptic longitude (0-360°)

    Returns:
        Dictionary with sign info
    """
    signs = [
        "Aries", "Taurus", "Gemini", "Cancer",
        "Leo", "Virgo", "Libra", "Scorpio",
        "Sagittarius", "Capricorn", "Aquarius", "Pisces"
    ]

    sign_num = int(longitude / 30)  # 0-11
    degree_in_sign = longitude % 30

    # DMS format (Degrees, Minutes, Seconds)
    degrees = int(degree_in_sign)
    minutes = int((degree_in_sign - degrees) * 60)
    seconds = int(((degree_in_sign - degrees) * 60 - minutes) * 60)

    return {
        'sign': signs[sign_num],
        'sign_num': sign_num,
        'degree': degree_in_sign,
        'dms': f"{degrees}°{minutes}'{seconds}\""
    }

# Example
sun_sign = longitude_to_sign(sidereal_sun)
print(f"Sun in {sun_sign['sign']} at {sun_sign['dms']}")

Part 4: Ascendant (Lagna) Calculation

The Ascendant is the zodiac degree rising on the eastern horizon at birth time. It requires precise location (latitude/longitude) and time.

def calculate_ascendant(jd, latitude, longitude, ayanamsa='lahiri'):
    """
    Calculate Ascendant and house cusps

    Args:
        jd: Julian Day
        latitude: Birth latitude in degrees
        longitude: Birth longitude in degrees
        ayanamsa: Ayanamsa system

    Returns:
        Dictionary with Ascendant and house cusps
    """
    # Set ayanamsa
    ayanamsa_modes = {
        'lahiri': swe.SIDM_LAHIRI,
        'raman': swe.SIDM_RAMAN,
        'krishnamurti': swe.SIDM_KRISHNAMURTI
    }
    swe.set_sid_mode(ayanamsa_modes.get(ayanamsa, swe.SIDM_LAHIRI))

    # House system: 'P' = Placidus, 'W' = Whole Sign, 'E' = Equal
    # Most Vedic astrologers use 'W' (Whole Sign)
    house_system = b'W'

    # Calculate houses
    cusps, ascmc = swe.houses_ex(
        jd,
        latitude,
        longitude,
        house_system
    )

    # ASCMC array contains:
    # [0] = Ascendant
    # [1] = MC (Midheaven)
    # [2] = ARMC (sidereal time)
    # [3] = Vertex

    ascendant_tropical = ascmc[0]
    mc_tropical = ascmc[1]

    # Convert to sidereal
    ayanamsa_value = swe.get_ayanamsa_ut(jd)
    ascendant = (ascendant_tropical - ayanamsa_value) % 360
    mc = (mc_tropical - ayanamsa_value) % 360

    # Calculate all 12 house cusps (Whole Sign)
    ascendant_sign = int(ascendant / 30)
    houses = [(ascendant_sign + i) % 12 * 30 for i in range(12)]

    return {
        'ascendant': ascendant,
        'mc': mc,
        'houses': houses,
        'ascendant_sign': longitude_to_sign(ascendant)
    }

# Example: Mumbai coordinates
jd = date_to_julian_day(1995, 3, 15, 8, 30, 0)
latitude = 19.0760  # Mumbai latitude
longitude = 72.8777  # Mumbai longitude

lagna = calculate_ascendant(jd, latitude, longitude, 'lahiri')

print(f"Ascendant: {lagna['ascendant']:.4f}°")
print(f"Lagna in: {lagna['ascendant_sign']['sign']}")
print(f"MC: {lagna['mc']:.4f}°")

Part 5: Nakshatra Calculation

The 27 Nakshatras (lunar mansions) divide the zodiac into 13°20' segments. Critical for Dasha calculations and compatibility matching.

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"
]

NAKSHATRA_LORDS = [
    "Ketu", "Venus", "Sun", "Moon", "Mars",
    "Rahu", "Jupiter", "Saturn", "Mercury", "Ketu",
    "Venus", "Sun", "Moon", "Mars", "Rahu",
    "Jupiter", "Saturn", "Mercury", "Ketu", "Venus",
    "Sun", "Moon", "Mars", "Rahu", "Jupiter",
    "Saturn", "Mercury"
]

def calculate_nakshatra(longitude):
    """
    Calculate Nakshatra from sidereal longitude

    Args:
        longitude: Sidereal ecliptic longitude (0-360°)

    Returns:
        Dictionary with nakshatra details
    """
    # Each nakshatra = 13°20' = 13.333...°
    nakshatra_length = 360 / 27

    nakshatra_num = int(longitude / nakshatra_length)

    # Each nakshatra has 4 padas (quarters)
    pada_length = nakshatra_length / 4
    degree_in_nakshatra = longitude % nakshatra_length
    pada = int(degree_in_nakshatra / pada_length) + 1

    return {
        'nakshatra': NAKSHATRAS[nakshatra_num],
        'nakshatra_num': nakshatra_num,
        'lord': NAKSHATRA_LORDS[nakshatra_num],
        'pada': pada,
        'degree_in_nakshatra': degree_in_nakshatra
    }

# Example: Moon at 247.8° sidereal
moon_nakshatra = calculate_nakshatra(247.8)

print(f"Nakshatra: {moon_nakshatra['nakshatra']}")
print(f"Lord: {moon_nakshatra['lord']}")
print(f"Pada: {moon_nakshatra['pada']}")

Part 6: Divisional Charts (Vargas)

Divisional charts divide each sign into smaller segments to analyze specific life areas. D9 (Navamsa) is second most important after D1 (Rasi).

Navamsa (D9) Calculation

def calculate_navamsa(longitude):
    """
    Calculate Navamsa (D9) position

    Each sign (30°) is divided into 9 parts of 3°20' each

    Args:
        longitude: Sidereal longitude (0-360°)

    Returns:
        Navamsa longitude
    """
    sign_num = int(longitude / 30)
    degree_in_sign = longitude % 30

    # Each navamsa = 3°20' = 3.333...°
    navamsa_in_sign = int(degree_in_sign / (30/9))

    # Navamsa sign calculation
    # Aries, Leo, Sagittarius (Fire): start from Aries
    # Taurus, Virgo, Capricorn (Earth): start from Capricorn
    # Gemini, Libra, Aquarius (Air): start from Libra
    # Cancer, Scorpio, Pisces (Water): start from Cancer

    sign_element = sign_num % 3

    if sign_element == 0:  # Fire signs (0, 3, 6, 9)
        start_sign = (sign_num // 3) * 3  # 0, 3, 6, 9
    elif sign_element == 1:  # Earth signs (1, 4, 7, 10)
        start_sign = 9  # Capricorn
    else:  # Water/Air
        start_sign = 6 if sign_num % 4 == 2 else 3

    navamsa_sign = (start_sign + navamsa_in_sign) % 12
    navamsa_longitude = navamsa_sign * 30

    return navamsa_longitude

# Simplified universal formula
def calculate_divisional_chart(longitude, division):
    """
    Universal divisional chart calculator

    Args:
        longitude: Sidereal longitude (0-360°)
        division: D9, D10, D12, D16, D20, D24, D27, D30, D40, D45, D60

    Returns:
        Divisional chart longitude
    """
    sign_num = int(longitude / 30)
    degree_in_sign = longitude % 30

    # Division within sign
    part = int(degree_in_sign / (30 / division))

    # Most divisional charts follow this pattern
    # For odd sign (0, 2, 4, 6, 8, 10): start from Aries
    # For even sign (1, 3, 5, 7, 9, 11): start from Libra (for odd divisions)

    if division % 2 == 1:  # Odd divisions (D9, D27, etc.)
        if sign_num % 2 == 0:
            divisional_sign = part
        else:
            divisional_sign = (6 + part) % 12
    else:  # Even divisions
        divisional_sign = ((sign_num * division) + part) % 12

    return divisional_sign * 30

# Example
sun_d9 = calculate_navamsa(271.5)
sun_d10 = calculate_divisional_chart(271.5, 10)

print(f"Sun D9 (Navamsa): {longitude_to_sign(sun_d9)['sign']}")
print(f"Sun D10 (Dashamsa): {longitude_to_sign(sun_d10)['sign']}")

All Major Divisional Charts

D1 (Rasi): Overall life, personality
D2 (Hora): Wealth, finances
D3 (Drekkana): Siblings, courage
D4 (Chaturthamsa): Property, assets
D7 (Saptamsa): Children, creativity
D9 (Navamsa): Marriage, dharma
D10 (Dashamsa): Career, profession
D12 (Dwadasamsa): Parents, ancestry
D16 (Shodasamsa): Vehicles, luxuries
D20 (Vimsamsa): Spiritual practices
D24 (Chaturvimsamsa): Education
D27 (Nakshatramsa): Strengths/weaknesses
D30 (Trimsamsa): Misfortunes, evils
D40 (Khavedamsa): Auspicious effects
D45 (Akshavedamsa): Overall results
D60 (Shashtiamsa): Past life karma

Part 7: Vimshottari Dasha Calculation

Dasha system calculates planetary periods based on Moon's nakshatra at birth. Most complex and critical calculation in Jyotish.

from datetime import datetime, timedelta

# Vimshottari period lengths (in years)
DASHA_YEARS = {
    'Ketu': 7,
    'Venus': 20,
    'Sun': 6,
    'Moon': 10,
    'Mars': 7,
    'Rahu': 18,
    'Jupiter': 16,
    'Saturn': 19,
    'Mercury': 17
}

# Dasha sequence
DASHA_SEQUENCE = ['Ketu', 'Venus', 'Sun', 'Moon', 'Mars', 'Rahu', 'Jupiter', 'Saturn', 'Mercury']

def calculate_vimshottari_dasha(moon_longitude, birth_datetime):
    """
    Calculate Vimshottari Dasha periods

    Args:
        moon_longitude: Moon's sidereal longitude at birth
        birth_datetime: Birth datetime object

    Returns:
        List of dasha periods with dates
    """
    # Get Moon's nakshatra
    moon_nak = calculate_nakshatra(moon_longitude)
    nakshatra_num = moon_nak['nakshatra_num']
    degree_in_nak = moon_nak['degree_in_nakshatra']

    # Determine starting Mahadasha lord
    lord_index = nakshatra_num % 9
    starting_lord = NAKSHATRA_LORDS[nakshatra_num]

    # Calculate balance of starting Mahadasha
    nakshatra_length = 360 / 27  # 13.333...°
    completed_fraction = degree_in_nak / nakshatra_length
    total_years = DASHA_YEARS[starting_lord]
    balance_years = total_years * (1 - completed_fraction)

    # Generate all Mahadashas
    dashas = []
    current_date = birth_datetime

    # First Mahadasha (balance period)
    end_date = current_date + timedelta(days=balance_years * 365.25)
    dashas.append({
        'planet': starting_lord,
        'start_date': current_date,
        'end_date': end_date,
        'years': balance_years
    })
    current_date = end_date

    # Remaining Mahadashas (complete 120-year cycle)
    lord_idx = DASHA_SEQUENCE.index(starting_lord)

    for i in range(1, 9):  # 8 more periods to complete cycle
        lord = DASHA_SEQUENCE[(lord_idx + i) % 9]
        years = DASHA_YEARS[lord]
        end_date = current_date + timedelta(days=years * 365.25)

        dashas.append({
            'planet': lord,
            'start_date': current_date,
            'end_date': end_date,
            'years': years
        })
        current_date = end_date

    return dashas

def calculate_antardasha(mahadasha_planet, maha_start, maha_end):
    """
    Calculate Antardasha (sub-periods) within a Mahadasha

    Args:
        mahadasha_planet: Mahadasha lord
        maha_start: Mahadasha start date
        maha_end: Mahadasha end date

    Returns:
        List of Antardasha periods
    """
    antardashas = []
    total_days = (maha_end - maha_start).days
    maha_years = DASHA_YEARS[mahadasha_planet]

    # Antardasha sequence starts with Mahadasha lord
    lord_idx = DASHA_SEQUENCE.index(mahadasha_planet)
    current_date = maha_start

    for i in range(9):
        antar_lord = DASHA_SEQUENCE[(lord_idx + i) % 9]
        antar_years = DASHA_YEARS[antar_lord]

        # Antardasha duration = (Antar_years × Maha_years) / 120
        antar_duration_years = (antar_years * maha_years) / 120
        antar_days = int(antar_duration_years * 365.25)

        end_date = current_date + timedelta(days=antar_days)

        # Ensure we don't exceed Mahadasha end
        if end_date > maha_end:
            end_date = maha_end

        antardashas.append({
            'planet': antar_lord,
            'start_date': current_date,
            'end_date': end_date,
            'months': antar_duration_years * 12
        })

        current_date = end_date

        if current_date >= maha_end:
            break

    return antardashas

# Example
moon_long = 247.8  # Moon at Jyeshtha
birth_date = datetime(1995, 3, 15, 8, 30, 0)

dashas = calculate_vimshottari_dasha(moon_long, birth_date)

print("Vimshottari Mahadasha Timeline:")
print("=" * 60)
for d in dashas[:5]:  # First 5 periods
    print(f"{d['planet']:10} {d['start_date'].year}-{d['end_date'].year} ({d['years']:.2f} years)")

Part 8: Yogas (Planetary Combinations)

Yogas are specific planetary combinations that produce results. Calculating yogas requires analyzing planet positions, aspects, and house ownership.

def check_raja_yoga(planets, ascendant):
    """
    Check for Raja Yoga (combinations of success/power)

    Raja Yoga occurs when:
    - Lords of Kendra (1,4,7,10) and Trikona (1,5,9) are conjunct
    - Or aspect each other

    Args:
        planets: Dictionary of planetary positions
        ascendant: Ascendant degree

    Returns:
        List of Raja Yogas present
    """
    yogas = []

    # Determine house ownership based on ascendant sign
    asc_sign = int(ascendant / 30)

    # Kendra houses: 1, 4, 7, 10
    # Trikona houses: 1, 5, 9

    # Simplified example: Jupiter-Venus conjunction
    jupiter_sign = int(planets['Jupiter']['longitude'] / 30)
    venus_sign = int(planets['Venus']['longitude'] / 30)

    if jupiter_sign == venus_sign:
        yogas.append({
            'name': 'Guru-Shukra Yoga',
            'description': 'Jupiter-Venus conjunction - wealth and wisdom',
            'strength': 'Strong'
        })

    return yogas

def check_gaja_kesari_yoga(planets):
    """
    Check for Gaja Kesari Yoga

    Occurs when Jupiter is in Kendra (1,4,7,10) from Moon
    One of the most auspicious yogas
    """
    moon_sign = int(planets['Moon']['longitude'] / 30)
    jupiter_sign = int(planets['Jupiter']['longitude'] / 30)

    # Calculate houses from Moon
    houses_from_moon = (jupiter_sign - moon_sign) % 12

    # Kendra from Moon: 0, 3, 6, 9 (1st, 4th, 7th, 10th houses)
    if houses_from_moon in [0, 3, 6, 9]:
        return {
            'present': True,
            'description': 'Jupiter in Kendra from Moon - fame, intelligence, prosperity',
            'strength': 'Strong' if houses_from_moon == 0 else 'Moderate'
        }

    return {'present': False}

def check_neecha_bhanga_yoga(planets):
    """
    Check for Neecha Bhanga Raja Yoga

    Cancellation of debilitation - turns weakness into strength
    """
    # Debilitation signs for each planet
    debilitation = {
        'Sun': 6,       # Libra
        'Moon': 7,      # Scorpio
        'Mars': 3,      # Cancer
        'Mercury': 11,  # Pisces
        'Jupiter': 9,   # Capricorn
        'Venus': 5,     # Virgo
        'Saturn': 0     # Aries
    }

    yogas = []

    for planet, deb_sign in debilitation.items():
        if planet in planets:
            planet_sign = int(planets[planet]['longitude'] / 30)

            if planet_sign == deb_sign:
                # Check for cancellation conditions
                # (simplified - actual calculation is more complex)
                yogas.append({
                    'planet': planet,
                    'debilitated': True,
                    'cancellation': 'Partial'  # Would check actual conditions
                })

    return yogas

Complete Birth Chart Calculator

Putting it all together - a complete birth chart calculator with all components:

class VedicBirthChart:
    def __init__(self, date, time, latitude, longitude, ayanamsa='lahiri'):
        """Complete Vedic birth chart calculator"""
        self.date = date
        self.time = time
        self.latitude = latitude
        self.longitude = longitude
        self.ayanamsa = ayanamsa

        # Calculate Julian Day
        self.jd = self._date_to_jd()

        # Calculate all components
        self.planets = self._calculate_planets()
        self.ascendant_data = self._calculate_ascendant()
        self.houses = self._calculate_houses()
        self.nakshatras = self._calculate_nakshatras()
        self.divisional_charts = self._calculate_divisional_charts()
        self.dashas = self._calculate_dashas()
        self.yogas = self._detect_yogas()

    def _date_to_jd(self):
        """Convert datetime to Julian Day"""
        year = self.date.year
        month = self.date.month
        day = self.date.day
        hour = self.time.hour
        minute = self.time.minute

        return date_to_julian_day(year, month, day, hour, minute, 0)

    def _calculate_planets(self):
        """Calculate all planetary positions"""
        planets = {}

        for name, planet_id in PLANETS.items():
            pos = get_planet_position(self.jd, planet_id)

            # Convert to sidereal
            sidereal_long, _ = convert_to_sidereal(
                pos['longitude'],
                self.jd,
                self.ayanamsa
            )

            planets[name] = {
                'longitude': sidereal_long,
                'latitude': pos['latitude'],
                'speed': pos['speed'],
                'retrograde': pos['retrograde'],
                'sign': longitude_to_sign(sidereal_long)
            }

        return planets

    def to_json(self):
        """Export complete chart as JSON"""
        return {
            'birthDetails': {
                'date': self.date.isoformat(),
                'time': self.time.isoformat(),
                'latitude': self.latitude,
                'longitude': self.longitude
            },
            'planets': self.planets,
            'ascendant': self.ascendant_data,
            'houses': self.houses,
            'nakshatras': self.nakshatras,
            'divisionalCharts': self.divisional_charts,
            'dashas': self.dashas[:5],  # First 5 Mahadashas
            'yogas': self.yogas
        }

# Usage
from datetime import datetime, time

birth_date = datetime(1995, 3, 15)
birth_time = time(8, 30, 0)

chart = VedicBirthChart(
    date=birth_date,
    time=birth_time,
    latitude=19.0760,  # Mumbai
    longitude=72.8777,
    ayanamsa='lahiri'
)

# Get complete chart
import json
print(json.dumps(chart.to_json(), indent=2))

Performance Optimization Tips

Calculation Optimization

  • Cache Swiss Ephemeris results: Planet positions don't change for the same JD
  • Batch calculations: Calculate all planets in one Swiss Ephemeris call
  • Precompute divisional charts: Store in database, recalculate only on chart updates
  • Use lookup tables: For nakshatra lords, sign rulers, aspect angles
  • Lazy loading: Calculate Dashas/Yogas only when requested

Common Calculation Errors to Avoid

Timezone Issues

Always convert birth time to UTC before calculating JD. Ignoring timezones causes 30-minute to 5-hour errors in Ascendant.

Wrong Ayanamsa

Using tropical positions for Vedic calculations. Always subtract ayanamsa. Difference is ~24° currently.

Modular Arithmetic Errors

Forgetting to apply % 360 or % 12 can cause longitude values outside valid ranges.

Floating Point Precision

Using integers for degree calculations loses precision. Use float64 for all astronomical calculations.

Skip the Complexity - Use Vedika API

Implementing accurate Jyotish calculations takes months of development. Vedika API provides Swiss Ephemeris accuracy, all divisional charts, dasha systems, and yoga detection via simple REST calls.

Frequently Asked Questions

What is ayanamsa in Vedic astrology?

Ayanamsa is the precession correction applied to convert tropical (Western) planetary positions to sidereal (Vedic) positions. Lahiri ayanamsa is most popular in India (24°09' in 2025). It accounts for the 23.4° axial precession of Earth, which causes a ~1° shift every 72 years.

How are divisional charts (Vargas) calculated?

Divisional charts divide each zodiac sign into equal parts. D9 (Navamsa) divides each 30° sign into 9 parts of 3°20' each. Formula: Navamsa_sign = ((sign_num × 9) + int(degree_in_sign / 3.333)) % 12. D10, D12, D16, D20, D24, D27, D30, D40, D45, D60 follow similar division patterns.

What is the difference between tropical and sidereal zodiac?

Tropical zodiac (Western) is fixed to seasons - Aries starts at spring equinox. Sidereal zodiac (Vedic) is fixed to constellations. Due to Earth's precession, they differ by ~24° (ayanamsa). Most Vedic astrologers use Lahiri ayanamsa for sidereal calculations.

How accurate are Swiss Ephemeris calculations?

Swiss Ephemeris provides planetary positions accurate to 0.001 arcseconds (milliarcsecond precision) for dates 3000 BCE to 3000 CE. It's the gold standard for astrology software, used by NASA and professional astronomers. Accuracy degrades slightly outside this range.

What house system should I use for Vedic astrology?

Most Vedic astrologers use Whole Sign houses where each house = one zodiac sign starting from Ascendant. Some use Equal House (30° divisions from Ascendant degree). Placidus, used in Western astrology, is rarely used in Jyotish. Whole Sign is simplest and most traditional.