Quick Start Guide
First Calculation
Interactive Mode (CLI)
The simplest way to get started:
ketu
Follow the prompts:
Enter a date (ISO format):
2020-12-21Enter a time:
19:20Enter a timezone:
Europe/Paris
Programming Mode
from datetime import datetime
from zoneinfo import ZoneInfo
from ketu.ephemeris.time import utc_to_julian
from ketu.display import print_positions, print_aspects
# Define the moment
paris = ZoneInfo("Europe/Paris")
dt = datetime(2020, 12, 21, 19, 20, tzinfo=paris)
# Convert to Julian day
jday = utc_to_julian(dt)
# Display positions
print_positions(jday)
Basic Calculations
Planetary Positions
from datetime import datetime
from ketu.ephemeris.time import utc_to_julian
from ketu.calculations import long, lat, dist_au
# Current date
dt = datetime.now()
jday = utc_to_julian(dt)
# Sun position (id=0)
sun_longitude = long(jday, 0)
sun_latitude = lat(jday, 0)
sun_distance = dist_au(jday, 0)
print(f"Sun: {sun_longitude:.2f}° longitude")
print(f" {sun_latitude:.2f}° latitude")
print(f" {sun_distance:.2f} AU")
Determine Zodiac Sign
import ketu
from ketu.calculations import long, body_sign
# Moon position
moon_long = long(jday, 1) # 1 = Moon
# Calculate sign
sign_data = body_sign(moon_long)
sign_index = sign_data[0]
degrees = sign_data[1]
minutes = sign_data[2]
print(f"Moon in {ketu.signs[sign_index]} {degrees}°{minutes}'")
Check Retrogradation
from ketu.calculations import is_retrograde
# Mercury (id=2)
if is_retrograde(jday, 2):
print("Mercury is retrograde")
else:
print("Mercury is direct")
Aspect Calculation
Aspects Between Two Planets
from ketu.aspects import get_aspect
import ketu.core
# Sun-Moon aspect
aspect = get_aspect(jday, 0, 1) # 0=Sun, 1=Moon
if aspect:
body1, body2, asp_type, orb = aspect
aspect_name = ketu.core.aspects["name"][asp_type].decode()
print(f"Sun-Moon: {aspect_name} (orb: {orb:.2f}°)")
else:
print("No Sun-Moon aspect")
Note
Use ketu.core.aspects (the aspect-type table) here, not ketu.aspects.
Because ketu/aspects/ is a subpackage, from ketu.aspects import ...
binds the name ketu.aspects to that module — so ketu.aspects["name"]
would raise TypeError: 'module' object is not subscriptable. The
structured table always lives at ketu.core.aspects.
All Current Aspects
from ketu.aspects import calculate_aspects
from ketu.calculations import body_name
import ketu.core
# Calculate all aspects
aspects_array = calculate_aspects(jday)
# Display
for aspect in aspects_array:
b1, b2, asp_idx, orb = aspect
name1 = body_name(b1)
name2 = body_name(b2)
asp_name = ketu.core.aspects["name"][asp_idx].decode()
print(f"{name1} - {name2}: {asp_name} ({orb:.2f}°)")
Complete Example: Natal Positions
from datetime import datetime
from zoneinfo import ZoneInfo
import ketu
import ketu.core
from ketu.ephemeris.time import utc_to_julian, local_to_utc
from ketu.calculations import long, body_sign, is_retrograde, body_name, positions
from ketu.aspects import calculate_aspects
def natal_positions(year, month, day, hour, minute, timezone_str):
"""Calculate planetary positions for a birth moment"""
# Create the date
tz = ZoneInfo(timezone_str)
dt = datetime(year, month, day, hour, minute, tzinfo=tz)
jday = utc_to_julian(dt)
print(f"\n{'='*50}")
print(f"NATAL POSITIONS - {dt.strftime('%d/%m/%Y %H:%M')} {timezone_str}")
print(f"{'='*50}\n")
# Planetary positions
print("PLANETARY POSITIONS:")
print("-" * 30)
for i, body in enumerate(ketu.bodies["name"]):
if i > 9: # Skip Rahu/Lilith to simplify
break
name = body.decode()
longitude = long(jday, i)
sign_data = body_sign(longitude)
sign = ketu.signs[sign_data[0]]
deg, min = sign_data[1], sign_data[2]
# Check retrogradation
retro = " R" if is_retrograde(jday, i) else ""
print(f"{name:8} : {sign:12} {deg:2}°{min:02}'{retro}")
# Major aspects
print(f"\nMAJOR ASPECTS:")
print("-" * 30)
aspects = calculate_aspects(jday)
for aspect in aspects:
b1, b2, asp_idx, orb = aspect
# Display only aspects with orb < 5°
if abs(orb) < 5:
name1 = body_name(b1)
name2 = body_name(b2)
asp_name = ketu.core.aspects["name"][asp_idx].decode()
print(f"{name1:8} {asp_name:12} {name2:8} ({orb:+.2f}°)")
# Usage
natal_positions(1990, 5, 15, 14, 30, "Europe/Paris")
Building a Full Natal Chart
New in v1.2: compute_chart returns a single structured array containing positions, house cusps, and aspect matrix.
from ketu.ephemeris.time import utc_to_julian
from ketu.charts import compute_chart
from datetime import datetime
# Paris, J2000 epoch
jd = 2451545.0 # 2000-01-01 12:00 UTC
lat, lon = 48.8566, 2.3522 # Paris
chart = compute_chart(jd, lat, lon, system="placidus")
# Ascendant
print(f"ASC: {chart['asc']:.2f}°")
# Sun longitude (body index 0)
print(f"Sun: {chart['body_lons'][0]:.2f}°")
# Chiron longitude (body index 13, new in v1.3)
print(f"Chiron: {chart['body_lons'][13]:.2f}°")
Tips and Tricks
Using the Cache
Position calculations use @lru_cache to optimize performance:
from ketu.calculations import long, lat
from ketu.ephemeris.planets import calc_planet_position
# These calls use the cache
long1 = long(jday, 0)
lat1 = lat(jday, 0) # Cache lives on calc_planet_position
# To clear the cache
calc_planet_position.cache_clear()
Working with NumPy
import numpy as np
from ketu.calculations import long, body_sign
# Calculate positions for multiple days
days = np.arange(jday, jday + 30, 1) # 30 days
sun_positions = [long(d, 0) for d in days]
# Find sign changes
signs = [body_sign(pos)[0] for pos in sun_positions]
changes = np.where(np.diff(signs))[0]
Next Steps
Explore Advanced Examples for complex use cases
See the API Reference for all details
Learn about Astrological Concepts used in Ketu
Discover House Systems for complete chart calculation