A lightweight, arcsecond-precision astrology engine for ±3000 years.
Pure Python. Zero native C/C++ dependencies. pip install and go.
Covers western tropical, Indian sidereal, and classical medieval techniques —
all from a single calculate_chart() call. Built on
taiyin-ephemeris-semi-analytic
(∼0.2–3.7″ RMS vs DE441) with Vondrák 2011 precession, IAU 2000B nutation,
WGS84 ellipsoid, and hybrid atmospheric refraction — hardcoded, no runtime
data files.
from lunaeph import calculate_chart
chart = calculate_chart(
2003, 3, 13, 14, 15, 0, # local date & time
tz=8.0, # UTC+8
latitude_deg=37.45, # Dongying, Shandong
longitude_deg=118.49,
)
# Planets
chart.planet("sun") # {'name': 'Sun', 'sign': 'Pisces', 'degree': 22, ...}
chart.planets # ['sun', 'moon', 'mercury', ...]
# Houses
chart.ascendant # {'sign': 'Leo', 'degree': 5, 'minute': 32}
chart.midheaven # {'sign': 'Aries', 'degree': 24, 'minute': 31}
# Aspects
chart.aspects_to("moon") # filter by body
chart.aspects_between("sun", "moon")
# Custom orbs
chart.set_orb(60, 1.0) # sextile → 1° orb
chart.reset_orb(60) # back to default| Category | What | Detail |
|---|---|---|
| Planets | 10 bodies + lunar nodes + Lilith | Sun through Pluto, True/Mean Node, True/Mean Lilith |
| Houses | 8 systems | Placidus, Koch, Whole Sign, Equal, Porphyry, Regiomontanus, Campanus, Alcabitius |
| Aspects | 12 angles + custom | Conjunction, opposition, trine, square, sextile, quincunx, semisextile, semisquare, sesquiquadrate, quintile, biquintile, decile; applying/separating via true velocity |
| Signs | Tropical + sidereal | 12 tropical signs (element/modality/triplicity); 6 ayanamshas (Lahiri, Fagan-Bradley, Raman, Krishnamurti, Yukteswar, True Chitra) |
| Dignities | Essential + accidental | Domicile, detriment, exaltation, fall, Dorothean triplicities, Chaldean faces, Ptolemaic & Egyptian terms, sect, combustion, cazimi |
| Lots | Hellenistic / Arabic Parts | Fortune, Spirit, Eros, Nemesis, Victory, Courage, Necessity, and more |
| Relationship | Synastry, Composite, Davison | Cross-chart aspects + house overlays; midpoint composite; time/space Davison |
| Predictive | Progressions + directions | Secondary, Tertiary I & II, Minor; True Solar Arc; Naibod direction |
| Returns | Solar + Lunar Returns | Exact Brent root-finding on true ecliptic longitude recurrence |
| Medieval | Almuten, Firdaria, ZR, Profections, Primary Directions | Ibn Ezra / Bonatti / Lilly schools; Valens/Brennan ZR; Naibod/Ptolemy keys |
| Jyotish | Nakshatra, Dasha, Vargas, Karakas | 27/28 Nakshatras with Ashta Kuta; Vimshottari (5-level) & Yogini Dasha; 16 Vargas (D1–D60); Chara/Sthira/Naisargika Karakas |
| Jyotish II | Ashtakavarga, Arudha, Upagraha, KP | Sarvashtakavarga; Jaimini Arudha Padas; 5 Upagrahas; KP sub-lord; Panchadha Maitri |
| Time utils | EoT, sunrise/sunset, syzygy | Equation of time; WGS84 ellipsoid + hybrid Bennett/Smart refraction; new/full moon sequence; apparent solar time |
from lunaeph import calculate_chart, HouseSystem
chart = calculate_chart(2003, 3, 13, 14, 15, 0, tz=8.0,
latitude_deg=37.45, longitude_deg=118.49,
house_system=HouseSystem.KOCH)
for i in range(1, 13):
cusp = chart.house_cusp(i)
print(f"House {i:2d}: {cusp['sign_abbrev']:>3s} {cusp['degree']:2d}°{cusp['minute']:02d}'")
# Custom orbs
chart.set_orb(72, 2.0) # quintile → 2°
chart.set_orb(100, 1.5) # custom angle → 1.5°# Dongying native (2003-03-13) × a friend (2002-12-01)
a = calculate_chart(2003, 3, 13, 14, 15, 0, tz=8.0,
latitude_deg=37.45, longitude_deg=118.49)
b = calculate_chart(2002, 12, 1, 8, 30, 0, tz=8.0,
latitude_deg=36.0, longitude_deg=120.0)
syn = a.synastry_with(b) # cross-chart aspects + house placements
comp = a.composite_with(b) # midpoint composite
dav = a.davison_with(b, mode="spherical") # Davison time/space chart
# Synastry cross-aspects (planet-to-planet only, top 5)
for entry in syn["cross_aspects"][:5]:
print(f"{entry['chart_a_body']:8s}─{entry['chart_b_body']:8s} "
f"{entry['aspect']:14s} {entry['orb_deg']:.2f}° "
f"{'A' if entry.get('applying') else 'S'}")
# sun ─moon quincunx 1.14° A
# sun ─mercury square 4.29° S
# sun ─mars biquintile 1.37° S
# sun ─saturn square 4.69° A
# sun ─neptune semisquare 1.42° Achart = calculate_chart(2003, 3, 13, 14, 15, 0, tz=8.0,
latitude_deg=37.45, longitude_deg=118.49)
prog = chart.secondary_progression(years=20.0) # 1 day = 1 year (age 20)
tert = chart.tertiary_progression(years=20.0) # Tertiary I
sa = chart.solar_arc(years=20.0) # True Solar Arc
sr23 = chart.solar_return(2023) # Solar Return for 2023
lr = chart.lunar_return(2023, 3) # Lunar Return for Mar 2023# Almuten Figuris (Victor of the Chart)
alm = chart.almuten_figuris() # → 'venus', score 33
# Firdaria (Persian time-lord periods)
from lunaeph import get_current_firdaria
fird = chart.firdaria(age=33.5) # active major & minor periods
# Zodiacal Releasing (Valens)
zr = chart.zodiacal_releasing(lot="spirit", age=33.5)
# Annual / Monthly / Daily Profections
prof = chart.profection(age=33.5)
# Primary Directions (Naibod key)
from lunaeph import calc_primary_directions
dirs = chart.primary_directions(key="naibod")
# Arabic Lots
from lunaeph import calculate_lots
lots = calculate_lots(asc_deg, sun_deg, moon_deg, ...)from lunaeph import calc_nakshatra_chart, calc_nakshatra_compatibility, get_nakshatra_info
# Planetary nakshatra placements (27 or 28 system)
nak = chart.nakshatra_chart(ayanamsha_mode="lahiri", system="27")
# → {'sun': {'nakshatra': 'Purva Bhadrapada', 'pada': 3, ...}, ...}
# Ashta Kuta compatibility
score = chart_a.nakshatra_compatibility(chart_b, ayanamsha_mode="lahiri")
# → {'total': 24.5, 'varna': 1, 'vashya': 2, 'tara': 3, ...}
# Single nakshatra info
info = get_nakshatra_info("Rohini")
# → {'lord': 'moon', 'deity': 'Brahma', 'gana': 'Manushya', ...}# Vimshottari Dasha (120-year, 5-level recursive)
dasha = chart.vimshottari_dasha(age=20.35, ayanamsha_mode="lahiri")
# → maha, antara, pratyantara, sookshma, prana breakdown
# Yogini Dasha (36-year cycle)
from lunaeph import calc_yogini_dasha
yog = chart.yogini_dasha(age_years=20.35)
# Convenience: current active period
from lunaeph import get_current_dasha
active = get_current_dasha(chart, age=20.35)# All 16 Vargas (D1 Rashi through D60 Shashtiamsha)
from lunaeph import calculate_divisional_charts
vargas = chart.divisional_charts(ayanamsha_mode="lahiri")
# → D1, D2 Hora, D3 Drekkana, D4, D7, D9 Navamsha, D10, D12, D16,
# D20, D24, D27 Bhamsha, D30 Trimshamsha, D40, D45, D60# Jaimini Chara Karakas (7 or 8 planet scheme)
k = chart.jaimini_chara_karakas(scheme="8_karaka")
# Ashtakavarga (Sarvashtakavarga, max 337 bindus)
av = chart.ashtakavarga() # → bhinnashtakavarga per planet + sarva totals
# Arudha Padas (A1–A12, including Upapada/UL)
ap = chart.arudha_padas() # → {'A1': ..., 'upapada': ...}
# Sade Sati (Saturn 7.5-year transit)
chart.sade_sati("Capricorn") # is Saturn transiting over natal Moon?
# Upagrahas (5 shadow planets)
chart.upagrahas() # → Dhuma, Vyatipata, Parivesha, Indrachapa, Upaketu
# KP system
chart.kp_sublord(planet="moon") # → star-lord + sub-lord
# All three karaka systems
from lunaeph import calc_all_karakas
all_k = chart.karakas() # → Naisargika, Sthira, Charafrom lunaeph import equation_of_time_minutes, apparent_solar_time_minutes
from lunaeph._time import calendar_to_jd
jd = calendar_to_jd(2024, 11, 3, 12, 0, 0.0)
eot = equation_of_time_minutes(jd) # → +16.48 min (sundial ahead of clock)
ast = apparent_solar_time_minutes(jd, lon_deg=116.4) # → minutes since midnightfrom lunaeph import sun_times
times = sun_times(2003, 3, 13, lon_deg=118.49, lat_deg=37.45, tz=8.0)
# → {'rise': JD, 'transit': JD, 'set': JD,
# 'civil_dawn': JD, 'civil_dusk': JD,
# 'nautical_dawn': JD, 'nautical_dusk': JD,
# 'astron_dawn': JD, 'astron_dusk': JD}
# Uses WGS84 ellipsoid + hybrid Bennett/Smart refraction model.
# Also accepts height_m, pressure_mbar, temperature_c for precision.from lunaeph import new_moons_between, full_moons_between, calendar_to_jd, jd_to_calendar
jd1 = calendar_to_jd(2003, 1, 1)
jd2 = calendar_to_jd(2004, 1, 1)
for jd in new_moons_between(jd1, jd2): # 12–13 per year
y, m, d, h, mi, s = jd_to_calendar(jd)
print(f"New moon: {y}-{m:02d}-{d:02d} {h:02d}:{mi:02d} UT")
# Accuracy: < 5 minutes vs USNO across −3000 to +3000.| Correction | Model |
|---|---|
| Planetary ephemeris | taiyin semi-analytic (~0.2–3.7″ RMS vs DE441) |
| Precession | Vondrák 2011 |
| Nutation | IAU 2000B (77 lunisolar + planetary terms) |
| ΔT | Stephenson & Morrison (2004/2015) spline + annual Catmull-Rom table |
| Light-time | single-pass iteration |
| Stellar aberration | special-relativistic velocity form |
| Solar deflection | GR deflection vector |
| Atmospheric refraction | Hybrid Bennett + Smart, scaled by P/T |
| Earth shape | WGS84 ellipsoid (a=6378137 m, 1/f=298.257223563) |
All models are hardcoded — no runtime data files, same philosophy as taiyin.
pip install lunaephDepends on taiyin-ephemeris-semi-analytic>=0.2.0.
Apache 2.0.
- 寿星天文历 (sxwnl) by 许剑伟 — https://github.com/sxwnl/sxwnl
The Equation of Time, sunrise/sunset/twilight, and syzygy sequence algorithms are adapted from this project. See NOTICE for the full copyright notice.