Chapter 15 — Cookbook: Practical Calculations from A to Z #
What You Will Learn #
This chapter is a collection of complete recipes, ready to copy and paste. Each recipe solves a common practical problem: from the birth chart to transits, from retrogrades to eclipses. There is no theory to read — just working code with its output.
Recipe 1 — Complete Birth Chart #
Problem: Given the date, time, and place of birth, calculate all planetary positions and houses.
What it is used for: The birth chart is the foundation of any astrological analysis. This code produces the “photograph of the sky” at the moment of birth: the positions of the planets in the signs, the twelve houses, and the main angles (Ascendant and Midheaven). From here all interpretations begin — from personality (Sun position) to emotions (Moon), to the way of communicating (Mercury), up to the structure of life areas (houses). A concrete application: an astrological software that automatically generates the birth chart starting from the user’s personal data.
import libephemeris as ephem
# Rome, April 8, 2024, 14:30 UT
jd = ephem.julday(2024, 4, 8, 14.5)
signs = ["Ari", "Tau", "Gem", "Cnc", "Leo", "Vir",
"Lib", "Sco", "Sgr", "Cap", "Aqr", "Psc"]
def zodiac_format(lon):
sign_idx = int(lon / 30)
degrees = lon % 30
d, m, s, sf, si = ephem.split_deg(degrees, 0)
return f'{d:2d}° {m:02d}\' {s:02d}" {signs[sign_idx]}'
bodies = [
(ephem.SE_SUN, "Sun"),
(ephem.SE_MOON, "Moon"),
(ephem.SE_MERCURY, "Mercury"),
(ephem.SE_VENUS, "Venus"),
(ephem.SE_MARS, "Mars"),
(ephem.SE_JUPITER, "Jupiter"),
(ephem.SE_SATURN, "Saturn"),
(ephem.SE_URANUS, "Uranus"),
(ephem.SE_NEPTUNE, "Neptune"),
(ephem.SE_PLUTO, "Pluto"),
(ephem.SE_MEAN_NODE, "North Node"),
(ephem.SE_CHIRON, "Chiron"),
]
print("--- Birth Chart: Rome, April 8, 2024, 14:30 ---")
print()
for body_id, name in bodies:
pos, _ = ephem.calc_ut(jd, body_id, ephem.SEFLG_SPEED)
retro = " R" if pos[3] < 0 else ""
print(f"{name:11s} {zodiac_format(pos[0])}{retro}")
# Placidus Houses — Rome (lat 41.9, lon 12.5)
cusps, ascmc = ephem.houses(jd, 41.9, 12.5, ord("P"))
print()
print(f"Ascendant: {zodiac_format(ascmc[0])}")
print(f"Midheaven: {zodiac_format(ascmc[1])}")
print()
for i in range(12):
print(f"House {i+1:2d}: {zodiac_format(cusps[i])}")
--- Birth Chart: Rome, April 8, 2024, 14:30 ---
Sun 19° 14' 34" Ari
Moon 16° 59' 41" Ari
Mercury 24° 53' 58" Ari R
Venus 4° 14' 49" Ari
Mars 12° 55' 36" Psc
Jupiter 19° 00' 36" Tau
Saturn 14° 26' 16" Psc
Uranus 21° 09' 46" Tau
Neptune 28° 11' 03" Psc
Pluto 1° 57' 56" Aqr
North Node 15° 39' 19" Ari R
Chiron 19° 23' 45" Ari
Ascendant: 12° 15' 00" Vir
Midheaven: 9° 02' 28" Gem
House 1: 12° 15' 00" Vir
House 2: 6° 21' 02" Lib
House 3: 5° 32' 08" Sco
House 4: 9° 02' 28" Sgr
House 5: 13° 32' 25" Cap
House 6: 15° 02' 03" Aqr
House 7: 12° 15' 00" Psc
House 8: 6° 21' 02" Ari
House 9: 5° 32' 08" Tau
House 10: 9° 02' 28" Gem
House 11: 13° 32' 25" Cnc
House 12: 15° 02' 03" Leo
Recipe 2 — Daily Transits #
Problem: Given a birth chart, find which transiting planets are in aspect with natal planets today.
What it is used for: Transits are the most widespread method of astrological forecasting. The principle is simple: “today’s” planets form significant angles (conjunction, square, trine…) with the positions of the birth chart, activating the themes represented by those planets. For example, Saturn squaring the natal Sun indicates a period of challenges and restructuring of one’s identity — typically lasting a few weeks. A concrete application: an app that every morning shows the user the active transits on their chart, along with the orb (how close it is to the exact aspect).
import libephemeris as ephem
# Natal positions: born March 15, 1990, 10:00 UT
jd_natal = ephem.julday(1990, 3, 15, 10.0)
# Transit date: April 8, 2024
jd_transit = ephem.julday(2024, 4, 8, 12.0)
planets = [
(ephem.SE_SUN, "Sun"), (ephem.SE_MOON, "Moon"),
(ephem.SE_MERCURY, "Mercury"), (ephem.SE_VENUS, "Venus"),
(ephem.SE_MARS, "Mars"), (ephem.SE_JUPITER, "Jupiter"),
(ephem.SE_SATURN, "Saturn"),
]
# Calculate natal and transit positions
natal_pos = {}
for body_id, name in planets:
pos, _ = ephem.calc_ut(jd_natal, body_id, 0)
natal_pos[name] = pos[0]
transit_pos = {}
for body_id, name in planets:
pos, _ = ephem.calc_ut(jd_transit, body_id, 0)
transit_pos[name] = pos[0]
# Define aspects and orbs
aspects = {0: "Conjunction", 60: "Sextile", 90: "Square",
120: "Trine", 180: "Opposition"}
orbs = {0: 8, 60: 6, 90: 7, 120: 7, 180: 8}
print("--- Transits of April 8, 2024, on March 15, 1990 chart ---")
print()
for t_name, t_lon in transit_pos.items():
for n_name, n_lon in natal_pos.items():
diff = abs(ephem.difdeg2n(t_lon, n_lon))
for asp_deg, asp_name in aspects.items():
orb = abs(diff - asp_deg)
if orb <= orbs[asp_deg]:
print(f"{t_name:10s} {asp_name:14s} {n_name:10s}"
f" (orb: {orb:.1f}°)")
break
--- Transits of April 8, 2024, on March 15, 1990 chart ---
Sun Square Saturn (orb: 4.2°)
Mercury Square Saturn (orb: 1.6°)
Venus Sextile Venus (orb: 4.9°)
Venus Sextile Mars (orb: 1.3°)
Venus Square Jupiter (orb: 2.7°)
Mars Trine Moon (orb: 4.1°)
Jupiter Sextile Sun (orb: 5.6°)
Jupiter Sextile Mercury (orb: 2.1°)
Jupiter Trine Saturn (orb: 4.3°)
Saturn Trine Moon (orb: 5.7°)
Saturn Conjunction Mercury (orb: 6.6°)
Recipe 3 — Sun Ingresses into Signs #
Problem: Find the exact date and time when the Sun enters each zodiac sign during the year.
What it is used for: The Sun’s ingresses into the signs mark the changes of season (Aries = spring equinox, Cancer = summer solstice, etc.) and are fundamental dates in mundane astrology. The chart erected for the moment of the Aries ingress (the so-called “chart of the year”) is used to make predictions for the entire year for a nation. In astronomy, knowing the exact moment of the equinox is useful for calibrating sundials and almanacs. A concrete application: an astrological calendar that automatically shows the sign change dates for any given year.
import libephemeris as ephem
jd = ephem.julday(2024, 1, 1, 0.0)
signs = ["Aries", "Taurus", "Gemini", "Cancer",
"Leo", "Virgo", "Libra", "Scorpio",
"Sagittarius", "Capricorn", "Aquarius", "Pisces"]
print("--- Sun Ingresses into Signs (2024) ---")
print()
for i in range(12):
target = i * 30.0
jd_ingress = ephem.solcross_ut(target, jd)
year, month, day, hour = ephem.revjul(jd_ingress)
hours = int(hour)
minutes = int((hour - hours) * 60)
print(f"{signs[i]:12s} {day:2d}/{month:02d}/{year}"
f" {hours:02d}:{minutes:02d} UT")
jd = jd_ingress + 1
--- Sun Ingresses into Signs (2024) ---
Aries 20/03/2024 03:06 UT
Taurus 19/04/2024 13:59 UT
Gemini 20/05/2024 12:59 UT
Cancer 20/06/2024 20:50 UT
Leo 22/07/2024 07:44 UT
Virgo 22/08/2024 14:55 UT
Libra 22/09/2024 12:43 UT
Scorpio 22/10/2024 22:14 UT
Sagittarius 21/11/2024 19:56 UT
Capricorn 21/12/2024 09:20 UT
Aquarius 19/01/2025 20:00 UT
Pisces 18/02/2025 10:06 UT
Recipe 4 — Mercury Retrograde #
Problem: Find all the retrograde and direct stations of Mercury in the year, and check if it is retrograde on a specific date.
What it is used for: Mercury retrograde is one of the most popular — and feared — astrological events. When Mercury is retrograde (meaning it appears to move backward as seen from Earth), astrological tradition associates the period with misunderstandings in communication, problems with contracts and technology, and travel delays. In practice, many people avoid signing contracts or buying electronics during these periods. This code finds the exact start (retrograde station) and end (direct station) dates of each retrograde, along with the precise zodiacal degree. A concrete application: a digital planner that automatically highlights Mercury retrograde periods.
import libephemeris as ephem
from libephemeris.crossing import swe_find_station_ut, is_retrograde
jd = ephem.julday(2024, 1, 1, 0.0)
jd_end = ephem.julday(2025, 1, 1, 0.0)
signs = ["Ari", "Tau", "Gem", "Cnc", "Leo", "Vir",
"Lib", "Sco", "Sgr", "Cap", "Aqr", "Psc"]
print("--- Mercury Retrogrades in 2024 ---")
print()
while jd < jd_end:
jd_station, type_ = swe_find_station_ut(
ephem.SE_MERCURY, jd, "any"
)
if jd_station >= jd_end:
break
year, month, day, hour = ephem.revjul(jd_station)
hours = int(hour)
minutes = int((hour - hours) * 60)
pos, _ = ephem.calc_ut(jd_station, ephem.SE_MERCURY, 0)
sign = signs[int(pos[0] / 30)]
degrees = pos[0] % 30
label = ("Retrograde Station" if type_ == "SR"
else "Direct Station ")
print(f"{label} {day:2d}/{month:02d}/{year}"
f" {hours:02d}:{minutes:02d} UT at {degrees:5.1f}° {sign}")
jd = jd_station + 1
# Point-in-time check
print()
retro = is_retrograde(ephem.SE_MERCURY,
ephem.julday(2024, 4, 8, 12.0))
print(f"Is Mercury retrograde on 8/4/2024? {retro}")
--- Mercury Retrogrades in 2024 ---
Direct Station 2/01/2024 03:07 UT at 22.2° Sgr
Retrograde Station 1/04/2024 22:14 UT at 27.2° Ari
Direct Station 25/04/2024 12:54 UT at 16.0° Ari
Retrograde Station 5/08/2024 04:56 UT at 4.1° Vir
Direct Station 28/08/2024 21:13 UT at 21.4° Leo
Retrograde Station 26/11/2024 02:42 UT at 22.7° Sgr
Direct Station 15/12/2024 20:56 UT at 6.4° Sgr
Is Mercury retrograde on 8/4/2024? True
Note:
swe_find_station_utandis_retrogradeare imported fromlibephemeris.crossingbecause they are not exposed at the main module level.
Recipe 5 — New Moon and Full Moon #
Problem: Find all the New and Full Moons of the year with date, time, and zodiac sign.
What it is used for: Lunar phases have set the rhythm of daily life for millennia. The New Moon (Sun-Moon conjunction) is traditionally the time to sow new projects, while the Full Moon (opposition) is the time of harvest and maximum energy — but also of emotional tension. In biodynamic agriculture, sowing still follows the lunar calendar. In astrology, the sign in which the New Moon falls indicates the “theme” of the month: a New Moon in Aries invites courageous action, one in Pisces to let go. A concrete application: a lunar calendar that shows the phases with their signs, useful for gardeners, fishermen, or anyone who follows lunar rhythms.
import libephemeris as ephem
jd = ephem.julday(2024, 1, 1, 0.0)
jd_end = ephem.julday(2025, 1, 1, 0.0)
signs = ["Ari", "Tau", "Gem", "Cnc", "Leo", "Vir",
"Lib", "Sco", "Sgr", "Cap", "Aqr", "Psc"]
print("--- New and Full Moons in 2024 ---")
print()
while jd < jd_end:
# Sun's current longitude
pos_sun, _ = ephem.calc_ut(jd, ephem.SE_SUN, 0)
lon_sun = pos_sun[0]
# New Moon: Moon conjuncts the Sun
jd_new = ephem.mooncross_ut(lon_sun, jd)
if jd_new < jd_end:
year, month, day, hour = ephem.revjul(jd_new)
hours = int(hour)
minutes = int((hour - hours) * 60)
pos_moon, _ = ephem.calc_ut(jd_new, ephem.SE_MOON, 0)
sign = signs[int(pos_moon[0] / 30)]
degrees = pos_moon[0] % 30
print(f"New Moon {day:2d}/{month:02d}/{year}"
f" {hours:02d}:{minutes:02d} UT"
f" in {degrees:5.1f}° {sign}")
# Full Moon: Moon opposite the Sun (180°)
target_full = (lon_sun + 180.0) % 360.0
jd_full = ephem.mooncross_ut(target_full, jd)
if jd_full < jd_end and jd_full > jd:
year, month, day, hour = ephem.revjul(jd_full)
hours = int(hour)
minutes = int((hour - hours) * 60)
pos_moon, _ = ephem.calc_ut(jd_full, ephem.SE_MOON, 0)
sign = signs[int(pos_moon[0] / 30)]
degrees = pos_moon[0] % 30
print(f"Full Moon {day:2d}/{month:02d}/{year}"
f" {hours:02d}:{minutes:02d} UT"
f" in {degrees:5.1f}° {sign}")
print()
# Advance by ~20 days to find the next pair
jd = min(jd_new, jd_full) + 20
if jd >= jd_end:
break
--- New and Full Moons in 2024 ---
New Moon 10/01/2024 18:20 UT in 10.0° Cap
Full Moon 23/01/2024 16:58 UT in 10.0° Cnc
New Moon 9/02/2024 06:34 UT in 10.3° Aqr
Full Moon 22/02/2024 10:11 UT in 10.3° Leo
New Moon 9/03/2024 17:05 UT in 10.2° Psc
Full Moon 23/03/2024 04:18 UT in 10.2° Vir
New Moon 8/04/2024 02:31 UT in 9.5° Ari
Full Moon 21/04/2024 22:20 UT in 9.5° Lib
New Moon 7/05/2024 11:18 UT in 8.3° Tau
Full Moon 21/05/2024 14:50 UT in 8.3° Sco
New Moon 5/06/2024 19:59 UT in 6.7° Gem
Full Moon 20/06/2024 04:55 UT in 6.7° Sgr
New Moon 5/07/2024 05:23 UT in 4.7° Cnc
Full Moon 19/07/2024 16:32 UT in 4.7° Cap
New Moon 3/08/2024 16:25 UT in 2.8° Leo
Full Moon 18/08/2024 02:22 UT in 2.8° Aqr
New Moon 2/09/2024 05:50 UT in 1.0° Vir
Full Moon 16/09/2024 11:18 UT in 1.0° Psc
New Moon 1/10/2024 21:45 UT in 29.7° Vir
Full Moon 15/10/2024 20:07 UT in 29.7° Psc
New Moon 31/10/2024 15:26 UT in 29.0° Lib
Full Moon 14/11/2024 05:21 UT in 29.0° Ari
New Moon 30/11/2024 09:32 UT in 28.8° Sco
Full Moon 13/12/2024 15:23 UT in 28.8° Tau
New Moon 30/12/2024 02:45 UT in 29.0° Sgr
Recipe 6 — Next Solar Eclipse from My City #
Problem: Find the next solar eclipses visible from a specific location, with contact times and magnitude.
What it is used for: Solar eclipses are spectacular but rare events for any given location — decades can pass between one total eclipse and the next. Knowing in advance when and how an eclipse will be visible from your city is essential for organizing observations (solar filters, travel, vacations). The magnitude indicates how much of the solar disk will be covered: 1.0 = total, 0.5 = half, 0.01 = barely perceptible. A concrete application: a website that, given the user’s location, shows the next visible eclipse with a map of the contacts.
import libephemeris as ephem
jd = ephem.julday(2024, 1, 1, 0.0)
lat, lon, alt = 41.9, 12.5, 0.0 # Rome
print("--- Next Solar Eclipses Visible from Rome ---")
print()
found = 0
for _ in range(20): # search up to 20 eclipses ahead
ecl_type, times, attr = ephem.sol_eclipse_when_loc(
jd, lat, lon, alt
)
if ecl_type > 0 and attr[0] > 0.01:
jd_max = times[0]
year, month, day, hour = ephem.revjul(jd_max)
hours = int(hour)
minutes = int((hour - hours) * 60)
type_ = "Partial"
if ecl_type & 4: type_ = "Annular"
if ecl_type & 1: type_ = "Total"
print(f"{type_:10s} {day:2d}/{month:02d}/{year}"
f" {hours:02d}:{minutes:02d} UT"
f" magnitude: {attr[0]:.3f}")
# Start and end times
if times[1] > 0:
_, _, _, h = ephem.revjul(times[1])
print(f" Start: {int(h):02d}:"
f"{int((h - int(h)) * 60):02d} UT")
if times[4] > 0:
_, _, _, h = ephem.revjul(times[4])
print(f" End: {int(h):02d}:"
f"{int((h - int(h)) * 60):02d} UT")
print()
found += 1
if found >= 3:
break
jd = times[0] + 30 if times[0] > 0 else jd + 180
--- Next Solar Eclipses Visible from Rome ---
Partial 29/03/2025 11:03 UT magnitude: 0.073
Start: 10:35 UT
End: 11:31 UT
Partial 2/08/2027 09:12 UT magnitude: 0.786
Start: 08:02 UT
End: 10:26 UT
Partial 1/06/2030 05:04 UT magnitude: 0.816
Start: 04:02 UT
End: 06:12 UT
Recipe 7 — Sidereal Chart (Vedic) #
Problem: Calculate planetary positions in the sidereal zodiac with Lahiri ayanamsha, including the Nakshatras.
What it is used for: Vedic astrology (Jyotish) uses the sidereal zodiac instead of the tropical one — positions are shifted by about 24° compared to Western ones. Nakshatras are 27 “lunar mansions” of 13°20’ each, further divided into 4 Padas; the position of the Moon in the Nakshatra is fundamental for calculating the Dashas (planetary periods that govern life). For example, someone with the Moon in the Ashwini Nakshatra will live their first period under Ketu (7 years), then under Venus (20 years), etc. A concrete application: a Jyotish software that generates the sidereal chart, the Nakshatras, and the calculation of the Dashas.
import libephemeris as ephem
from libephemeris.constants import (SE_SIDM_LAHIRI,
SEFLG_SIDEREAL, SEFLG_SPEED)
jd = ephem.julday(2024, 4, 8, 14.5)
ephem.swe_set_sid_mode(SE_SIDM_LAHIRI)
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 Bhadra",
"Uttara Bhadra", "Revati",
]
signs = ["Mesh", "Vrish", "Mithun", "Kark", "Simha",
"Kanya", "Tula", "Vrischik", "Dhanu", "Makar",
"Kumbh", "Meen"]
bodies = [
(ephem.SE_SUN, "Surya"),
(ephem.SE_MOON, "Chandra"),
(ephem.SE_MARS, "Mangal"),
(ephem.SE_MERCURY, "Budha"),
(ephem.SE_JUPITER, "Guru"),
(ephem.SE_VENUS, "Shukra"),
(ephem.SE_SATURN, "Shani"),
(ephem.SE_MEAN_NODE, "Rahu"),
]
ayan = ephem.swe_get_ayanamsa_ut(jd)
print("--- Sidereal Chart (Lahiri) ---")
print(f"Ayanamsha: {ayan:.4f}°")
print()
for body_id, name in bodies:
pos, _ = ephem.calc_ut(jd, body_id,
SEFLG_SPEED | SEFLG_SIDEREAL)
lon = pos[0]
sign = signs[int(lon / 30)]
degrees = lon % 30
nak_idx = int(lon / (360 / 27))
nak = nakshatras[nak_idx]
pada = int((lon % (360 / 27)) / (360 / 108)) + 1
print(f"{name:8s} {degrees:5.1f}° {sign:10s}"
f" Nakshatra: {nak} (Pada {pada})")
# Ketu = opposite of Rahu
pos_rahu, _ = ephem.calc_ut(jd, ephem.SE_MEAN_NODE,
SEFLG_SPEED | SEFLG_SIDEREAL)
ketu = (pos_rahu[0] + 180) % 360
sign_k = signs[int(ketu / 30)]
degrees_k = ketu % 30
nak_k = nakshatras[int(ketu / (360 / 27))]
pada_k = int((ketu % (360 / 27)) / (360 / 108)) + 1
print(f"Ketu {degrees_k:5.1f}° {sign_k:10s}"
f" Nakshatra: {nak_k} (Pada {pada_k})")
ephem.swe_set_sid_mode(0) # reset
--- Sidereal Chart (Lahiri) ---
Ayanamsha: 24.1961°
Surya 25.0° Meen Nakshatra: Revati (Pada 3)
Chandra 22.8° Meen Nakshatra: Revati (Pada 2)
Mangal 18.7° Kumbh Nakshatra: Shatabhisha (Pada 4)
Budha 0.7° Mesh Nakshatra: Ashwini (Pada 1)
Guru 24.8° Mesh Nakshatra: Bharani (Pada 4)
Shukra 10.1° Meen Nakshatra: Uttara Bhadra (Pada 3)
Shani 20.2° Kumbh Nakshatra: Purva Bhadra (Pada 1)
Rahu 21.5° Meen Nakshatra: Revati (Pada 2)
Ketu 21.5° Kanya Nakshatra: Hasta (Pada 4)
Recipe 8 — Visibility of Planets Tonight #
Problem: For every planet visible to the naked eye, calculate its altitude, direction, magnitude, and elongation from the Sun one hour after sunset.
What it is used for: Knowing which planets are visible tonight is the most practical question for stargazers. The altitude above the horizon tells whether the planet is high enough to be seen (above buildings and trees); the elongation from the Sun tells whether it is immersed in the twilight glare; the magnitude tells how bright it is (under 6 = visible to the naked eye, under 0 = very bright). For example, Jupiter at magnitude -2.0 and an altitude of 15° to the east is unmistakable — it is the “bright point” that many mistake for an airplane. A concrete application: an app for amateur astronomers that shows “what’s in the sky tonight” with direction and brightness.
import libephemeris as ephem
# Rome, April 8, 2024
lat, lon = 41.9, 12.5
jd_noon = ephem.julday(2024, 4, 8, 12.0)
# Find the exact sunset time
jd_set, _ = ephem.rise_trans(
jd_noon, ephem.SE_SUN, lat, lon, rsmi=2
)
year, month, day, hour = ephem.revjul(jd_set)
hours_s = int(hour)
minutes_s = int((hour - hours_s) * 60)
print(f"Sunset: {hours_s:02d}:{minutes_s:02d} UT")
print()
# One hour after sunset
jd_evening = jd_set + 1.0 / 24.0
ephem.set_topo(lon, lat, 0)
pos_sun, _ = ephem.calc_ut(jd_evening, ephem.SE_SUN, 0)
planets = [
(ephem.SE_MERCURY, "Mercury"),
(ephem.SE_VENUS, "Venus"),
(ephem.SE_MARS, "Mars"),
(ephem.SE_JUPITER, "Jupiter"),
(ephem.SE_SATURN, "Saturn"),
]
dirs = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"]
print("--- Planet Visibility (Rome, 8/4/2024) ---")
print()
for body_id, name in planets:
pos, _ = ephem.calc_ut(jd_evening, body_id, 0)
# Altitude and azimuth
az, alt_t, alt_app = ephem.azalt(
jd_evening, 0, (lon, lat, 0),
1013.25, 15.0, (pos[0], pos[1], pos[2])
)
# Elongation from the Sun
elong = abs(ephem.difdeg2n(pos[0], pos_sun[0]))
# Apparent magnitude
pheno, _ = ephem.swe_pheno_ut(jd_evening, body_id, 0)
mag = pheno[4]
# Cardinal direction
dir_idx = int((az + 22.5) / 45) % 8
direction = dirs[dir_idx]
visible = ("VISIBLE" if alt_app > 5 and elong > 15
else "not visible")
print(f"{name:10s} alt: {alt_app:5.1f}°"
f" az: {az:5.1f}° ({direction:2s})"
f" mag: {mag:+.1f}"
f" elong: {elong:5.1f}°"
f" {visible}")
Sunset: 17:43 UT
--- Planet Visibility (Rome, 8/4/2024) ---
Mercury alt: -5.5° az: 111.8° (E ) mag: +4.8 elong: 5.4° not visible
Venus alt: -25.8° az: 116.4° (SE) mag: -3.9 elong: 15.0° not visible
Mars alt: -44.6° az: 129.0° (SE) mag: +1.2 elong: 36.4° not visible
Jupiter alt: 15.5° az: 98.8° (E ) mag: -2.0 elong: 29.6° VISIBLE
Saturn alt: -43.6° az: 127.4° (SE) mag: +1.1 elong: 35.0° not visible
On this evening in April 2024, only Jupiter is visible — low in the east, very bright (mag -2.0). The other planets are below the horizon or too close to the Sun.
Recipe 9 — Aspects Between Planets #
Problem: Calculate the angular distance between all planets and identify the active aspects (conjunction, sextile, square, trine, opposition).
What it is used for: Aspects are the angular relationships between planets — the fundamental language of astrology. A conjunction (0°) merges the energies of the two planets; a trine (120°) harmonizes them; a square (90°) puts them in tension. For example, Sun conjunct Moon (as on April 8, 2024) indicates a New Moon — a time of emotional reset and new beginnings. Mars conjunct Saturn indicates a phase of frustration but also of constructive discipline. Knowing whether an aspect is “applying” (forming) or “separating” (dissolving) changes the interpretation: an applying aspect is more intense. A concrete application: an automatic interpretation module that reads the aspects and generates a descriptive text.
import libephemeris as ephem
jd = ephem.julday(2024, 4, 8, 12.0)
planets = [
(ephem.SE_SUN, "Sun"),
(ephem.SE_MOON, "Moon"),
(ephem.SE_MERCURY, "Mercury"),
(ephem.SE_VENUS, "Venus"),
(ephem.SE_MARS, "Mars"),
(ephem.SE_JUPITER, "Jupiter"),
(ephem.SE_SATURN, "Saturn"),
]
# Calculate positions and velocities
positions = {}
for body_id, name in planets:
pos, _ = ephem.calc_ut(jd, body_id, ephem.SEFLG_SPEED)
positions[name] = (pos[0], pos[3]) # longitude, velocity
aspects = {0: "Conjunction", 60: "Sextile",
90: "Square", 120: "Trine",
180: "Opposition"}
max_orbs = {0: 8, 60: 5, 90: 6, 120: 6, 180: 8}
print("--- Aspects Between Planets (April 8, 2024) ---")
print()
names = list(positions.keys())
for i in range(len(names)):
for j in range(i + 1, len(names)):
n1, n2 = names[i], names[j]
lon1, vel1 = positions[n1]
lon2, vel2 = positions[n2]
diff = abs(ephem.difdeg2n(lon1, lon2))
for asp_deg, asp_name in aspects.items():
orb = abs(diff - asp_deg)
if orb <= max_orbs[asp_deg]:
print(f"{n1:10s} {asp_name:14s} {n2:10s}"
f" orb: {orb:4.1f}°")
break
--- Aspects Between Planets (April 8, 2024) ---
Sun Conjunction Moon orb: 3.7°
Sun Conjunction Mercury orb: 5.8°
Mars Conjunction Saturn orb: 1.6°
Jupiter Sextile Saturn orb: 4.6°
Recipe 10 — Solar Return #
Problem: Find the exact moment when the Sun returns to the same longitude as at birth, and calculate the chart for that instant.
What it is used for: The solar return is one of the most widely used predictive techniques in astrology. Every year, the Sun returns exactly to the degree it was at the moment of birth — that precise instant generates a “chart of the year” that is superimposed on the birth chart. The Ascendant of the solar return indicates the “tone” of the year: for example, an Ascendant in Virgo suggests a year dedicated to work, organization, and health. The position of the Moon in the return indicates the emotional climate of the next twelve months. A concrete application: a software that automatically generates the solar return for every birthday and compares it with the birth chart.
import libephemeris as ephem
# Born March 15, 1990, 10:00 UT, Rome
jd_natal = ephem.julday(1990, 3, 15, 10.0)
pos_natal, _ = ephem.calc_ut(jd_natal, ephem.SE_SUN, 0)
lon_natal = pos_natal[0]
# Find the solar return for 2024
jd_search = ephem.julday(2024, 3, 1, 0.0)
jd_return = ephem.solcross_ut(lon_natal, jd_search)
year, month, day, hour = ephem.revjul(jd_return)
hours = int(hour)
minutes = int((hour - hours) * 60)
seconds = int(((hour - hours) * 60 - minutes) * 60)
signs = ["Ari", "Tau", "Gem", "Cnc", "Leo", "Vir",
"Lib", "Sco", "Sgr", "Cap", "Aqr", "Psc"]
print("--- Solar Return 2024 ---")
print()
print(f"Natal Sun: {lon_natal:.6f}°")
print(f"Return: {day:2d}/{month:02d}/{year}"
f" {hours:02d}:{minutes:02d}:{seconds:02d} UT")
print()
bodies = [
(ephem.SE_SUN, "Sun"),
(ephem.SE_MOON, "Moon"),
(ephem.SE_MERCURY, "Mercury"),
(ephem.SE_VENUS, "Venus"),
(ephem.SE_MARS, "Mars"),
(ephem.SE_JUPITER, "Jupiter"),
(ephem.SE_SATURN, "Saturn"),
]
for body_id, name in bodies:
pos, _ = ephem.calc_ut(jd_return, body_id, 0)
sign = signs[int(pos[0] / 30)]
degrees = pos[0] % 30
d, m, s, sf, si = ephem.split_deg(degrees, 0)
print(f"{name:10s} {d:2d}° {m:02d}' {sign}")
# Placidus Houses for Rome
cusps, ascmc = ephem.houses(
jd_return, 41.9, 12.5, ord("P")
)
print()
print(f"SR Ascendant: {ascmc[0]:.2f}°"
f" ({signs[int(ascmc[0] / 30)]})")
print(f"SR Midheaven: {ascmc[1]:.2f}°"
f" ({signs[int(ascmc[1] / 30)]})")
--- Solar Return 2024 ---
Natal Sun: 354.556761°
Return: 14/03/2024 15:50:28 UT
Sun 24° 33' Psc
Moon 23° 25' Tau
Mercury 8° 25' Ari
Venus 3° 24' Psc
Mars 23° 31' Aqr
Jupiter 13° 48' Tau
Saturn 11° 34' Psc
SR Ascendant: 158.76° (Vir)
SR Midheaven: 64.82° (Gem)
Recipe 11 — Planetary Hours #
Problem: Calculate the planetary hours of the day, dividing the time between sunrise and sunset into 12 parts and assigning a planet to each according to the Chaldean sequence.
What it is used for: Planetary hours are one of the oldest systems of time organization, dating back to Babylon. The idea is that every hour of the day is “ruled” by a planet, following the Chaldean sequence (Saturn → Jupiter → Mars → Sun → Venus → Mercury → Moon, and then it repeats). The first diurnal hour is ruled by the planet of the day: Monday = Moon, Tuesday = Mars, etc. A planetary hour does not last 60 minutes — it lasts 1/12 of the daylight period (in summer, diurnal hours are longer). In horary astrology and ceremonial magic, one chooses the hour of the appropriate planet to start an activity: the hour of Venus for matters of love, the hour of Mercury to sign contracts, the hour of Jupiter for financial affairs. A concrete application: an app that shows in real-time which planetary hour is currently active, with a countdown to the next one.
import libephemeris as ephem
lat, lon = 41.9, 12.5 # Rome
jd_noon = ephem.julday(2024, 4, 8, 12.0)
# Find sunrise, sunset, and next sunrise
jd_rise, _ = ephem.rise_trans(
jd_noon - 0.5, ephem.SE_SUN, lat, lon, rsmi=1
)
jd_set, _ = ephem.rise_trans(
jd_noon, ephem.SE_SUN, lat, lon, rsmi=2
)
jd_rise_tmrw, _ = ephem.rise_trans(
jd_set, ephem.SE_SUN, lat, lon, rsmi=1
)
_, _, _, h_rise = ephem.revjul(jd_rise)
_, _, _, h_set = ephem.revjul(jd_set)
print(f"Sunrise: {int(h_rise):02d}:"
f"{int((h_rise - int(h_rise)) * 60):02d} UT")
print(f"Sunset: {int(h_set):02d}:"
f"{int((h_set - int(h_set)) * 60):02d} UT")
# Duration of a planetary hour
day_hour = (jd_set - jd_rise) / 12
night_hour = (jd_rise_tmrw - jd_set) / 12
print(f"Day hour duration: {day_hour * 24 * 60:.1f} min")
print(f"Night hour duration: {night_hour * 24 * 60:.1f} min")
print()
# Chaldean sequence
chaldean = ["Saturn", "Jupiter", "Mars", "Sun",
"Venus", "Mercury", "Moon"]
# April 8, 2024 = Monday -> Moon (index 6)
ruler = 6
print("--- Planetary Hours (Monday 8/4/2024, Rome) ---")
print()
print("Day hours:")
idx = ruler
for i in range(12):
start = jd_rise + i * day_hour
end = jd_rise + (i + 1) * day_hour
_, _, _, hi = ephem.revjul(start)
_, _, _, hf = ephem.revjul(end)
print(f" Hour {i+1:2d} "
f"{int(hi):02d}:{int((hi-int(hi))*60):02d}-"
f"{int(hf):02d}:{int((hf-int(hf))*60):02d} UT"
f" {chaldean[idx % 7]}")
idx += 1
print()
print("Night hours:")
for i in range(12):
start = jd_set + i * night_hour
end = jd_set + (i + 1) * night_hour
_, _, _, hi = ephem.revjul(start)
_, _, _, hf = ephem.revjul(end)
print(f" Hour {i+1:2d} "
f"{int(hi):02d}:{int((hi-int(hi))*60):02d}-"
f"{int(hf):02d}:{int((hf-int(hf))*60):02d} UT"
f" {chaldean[idx % 7]}")
idx += 1
Sunrise: 04:40 UT
Sunset: 17:43 UT
Day hour duration: 65.2 min
Night hour duration: 54.6 min
--- Planetary Hours (Monday 8/4/2024, Rome) ---
Day hours:
Hour 1 04:40-05:45 UT Moon
Hour 2 05:45-06:51 UT Saturn
Hour 3 06:51-07:56 UT Jupiter
Hour 4 07:56-09:01 UT Mars
Hour 5 09:01-10:06 UT Sun
Hour 6 10:06-11:12 UT Venus
Hour 7 11:12-12:17 UT Mercury
Hour 8 12:17-13:22 UT Moon
Hour 9 13:22-14:27 UT Saturn
Hour 10 14:27-15:33 UT Jupiter
Hour 11 15:33-16:38 UT Mars
Hour 12 16:38-17:43 UT Sun
Night hours:
Hour 1 17:43-18:38 UT Venus
Hour 2 18:38-19:32 UT Mercury
Hour 3 19:32-20:27 UT Moon
Hour 4 20:27-21:22 UT Saturn
Hour 5 21:22-22:16 UT Jupiter
Hour 6 22:16-23:11 UT Mars
Hour 7 23:11-00:05 UT Sun
Hour 8 00:05-01:00 UT Venus
Hour 9 01:00-01:55 UT Mercury
Hour 10 01:55-02:49 UT Moon
Hour 11 02:49-03:44 UT Saturn
Hour 12 03:44-04:38 UT Jupiter
Note how the diurnal hours (65 min) are longer than the nocturnal ones (55 min) — in April in the Northern Hemisphere, the day is longer than the night.
Recipe 12 — Printable Monthly Ephemeris #
Problem: Generate a table with the position of all planets for each day of the month, in zodiacal format.
What it is used for: The ephemeris is the astrologer’s working tool — a table showing “where the planets are” every day at noon. Before computers, astrologers consulted voluminous ephemeris books (such as the famous Raphael’s Ephemeris) to calculate charts manually. Even today, a compact table is useful to get an overview of the month: you can see at a glance when a planet changes sign, when the Moon quickly transits through the zodiac, and when Mercury slows down before its retrograde. A concrete application: a generator of customized PDF ephemerides for any given year.
import libephemeris as ephem
signs = ["Ari", "Tau", "Gem", "Cnc", "Leo", "Vir",
"Lib", "Sco", "Sgr", "Cap", "Aqr", "Psc"]
def fmt(lon):
"""Format longitude in degrees + abbreviated sign."""
s = signs[int(lon / 30)]
d = lon % 30
return f"{d:5.1f}{s}"
bodies = [
(ephem.SE_SUN, "Sun"),
(ephem.SE_MOON, "Moon"),
(ephem.SE_MERCURY, "Merc"),
(ephem.SE_VENUS, "Venu"),
(ephem.SE_MARS, "Mars"),
(ephem.SE_JUPITER, "Jupi"),
(ephem.SE_SATURN, "Satu"),
]
print("--- Ephemeris April 2024 (Noon UT) ---")
print()
header = "Date "
for _, name in bodies:
header += f" {name:>8s}"
print(header)
print("-" * len(header))
for day in range(1, 31):
jd = ephem.julday(2024, 4, day, 12.0)
row = f"{day:2d}/04/2024"
for body_id, _ in bodies:
pos, _ = ephem.calc_ut(jd, body_id, 0)
row += f" {fmt(pos[0]):>8s}"
print(row)
--- Ephemeris April 2024 (Noon UT) ---
Date Sun Moon Merc Venu Mars Jupi Satu
------------------------------------------------------------------------------
1/04/2024 12.2Ari 4.4Cap 27.2Ari 25.5Psc 7.4Psc 17.5Tau 13.7Psc
2/04/2024 13.2Ari 17.8Cap 27.2Ari 26.7Psc 8.2Psc 17.7Tau 13.8Psc
3/04/2024 14.2Ari 1.7Aqr 27.1Ari 27.9Psc 9.0Psc 17.9Tau 13.9Psc
4/04/2024 15.2Ari 15.9Aqr 26.8Ari 29.2Psc 9.7Psc 18.1Tau 14.0Psc
5/04/2024 16.2Ari 0.5Psc 26.5Ari 0.4Ari 10.5Psc 18.3Tau 14.1Psc
6/04/2024 17.2Ari 15.3Psc 26.1Ari 1.6Ari 11.3Psc 18.5Tau 14.2Psc
7/04/2024 18.2Ari 0.4Ari 25.6Ari 2.9Ari 12.1Psc 18.8Tau 14.3Psc
8/04/2024 19.1Ari 15.4Ari 25.0Ari 4.1Ari 12.8Psc 19.0Tau 14.4Psc
9/04/2024 20.1Ari 0.4Tau 24.3Ari 5.4Ari 13.6Psc 19.2Tau 14.5Psc
10/04/2024 21.1Ari 15.1Tau 23.6Ari 6.6Ari 14.4Psc 19.4Tau 14.6Psc
11/04/2024 22.1Ari 29.4Tau 22.9Ari 7.8Ari 15.2Psc 19.7Tau 14.7Psc
12/04/2024 23.1Ari 13.4Gem 22.1Ari 9.1Ari 16.0Psc 19.9Tau 14.9Psc
13/04/2024 24.0Ari 26.8Gem 21.4Ari 10.3Ari 16.7Psc 20.1Tau 15.0Psc
14/04/2024 25.0Ari 9.9Cnc 20.6Ari 11.5Ari 17.5Psc 20.3Tau 15.1Psc
15/04/2024 26.0Ari 22.6Cnc 19.9Ari 12.8Ari 18.3Psc 20.6Tau 15.2Psc
16/04/2024 27.0Ari 4.9Leo 19.2Ari 14.0Ari 19.1Psc 20.8Tau 15.3Psc
17/04/2024 28.0Ari 17.0Leo 18.6Ari 15.2Ari 19.8Psc 21.0Tau 15.4Psc
18/04/2024 28.9Ari 28.9Leo 18.0Ari 16.5Ari 20.6Psc 21.2Tau 15.5Psc
19/04/2024 29.9Ari 10.8Vir 17.5Ari 17.7Ari 21.4Psc 21.5Tau 15.6Psc
20/04/2024 0.9Tau 22.5Vir 17.0Ari 18.9Ari 22.2Psc 21.7Tau 15.7Psc
21/04/2024 1.9Tau 4.4Lib 16.7Ari 20.2Ari 22.9Psc 21.9Tau 15.8Psc
22/04/2024 2.8Tau 16.3Lib 16.4Ari 21.4Ari 23.7Psc 22.2Tau 15.9Psc
23/04/2024 3.8Tau 28.3Lib 16.2Ari 22.6Ari 24.5Psc 22.4Tau 16.0Psc
24/04/2024 4.8Tau 10.5Sco 16.0Ari 23.9Ari 25.3Psc 22.6Tau 16.0Psc
25/04/2024 5.8Tau 22.9Sco 16.0Ari 25.1Ari 26.0Psc 22.8Tau 16.1Psc
26/04/2024 6.7Tau 5.5Sgr 16.0Ari 26.3Ari 26.8Psc 23.1Tau 16.2Psc
27/04/2024 7.7Tau 18.3Sgr 16.1Ari 27.6Ari 27.6Psc 23.3Tau 16.3Psc
28/04/2024 8.7Tau 1.3Cap 16.3Ari 28.8Ari 28.3Psc 23.5Tau 16.4Psc
29/04/2024 9.7Tau 14.6Cap 16.6Ari 0.0Tau 29.1Psc 23.8Tau 16.5Psc
30/04/2024 10.6Tau 28.1Cap 17.0Ari 1.3Tau 29.9Psc 24.0Tau 16.6Psc
From the table it can be clearly read: Mercury moves backward from 27.2° Ari to 16.0° Ari (direct station on 25/04), then starts advancing again. The Moon traverses the entire zodiac in a month. The Sun advances about 1° per day through Aries and Taurus.
Summary #
This chapter has presented 12 complete recipes for the most common astronomical and astrological calculations:
- Recipe 1 — Complete birth chart with planets, houses, and angles
- Recipe 2 — Daily transits on a birth chart
- Recipe 3 — Sun ingresses into the 12 signs (equinoxes and solstices)
- Recipe 4 — Mercury retrogrades (stations and verification)
- Recipe 5 — New and Full Moons of the year
- Recipe 6 — Next solar eclipses visible from a city
- Recipe 7 — Vedic sidereal chart with Nakshatras and Padas
- Recipe 8 — Visibility of planets tonight (altitude, magnitude, direction)
- Recipe 9 — Aspects between planets with orb
- Recipe 10 — Solar Return
- Recipe 11 — Planetary hours (Chaldean sequence)
- Recipe 12 — Printable monthly ephemeris
Each recipe is self-contained and ready to use: just copy the code, modify the date and coordinates, and run it.
Functions Used in the Recipes #
julday(y, m, d, h)/revjul(jd)— date ↔ Julian Day conversioncalc_ut(jd, body, flag)— position of a celestial bodyhouses(jd, lat, lon, hsys)— house cusps and anglessplit_deg(deg, flag)— splits decimal degrees into °, ', "difdeg2n(p1, p2)— normalized angular difference [-180, 180]solcross_ut(x, jd)— Sun crossing at a longitudemooncross_ut(x, jd)— Moon crossing at a longitudeswe_find_station_ut(body, jd, type)— next retrograde/direct station (fromlibephemeris.crossing)is_retrograde(body, jd)— retrograde verification (fromlibephemeris.crossing)sol_eclipse_when_loc(jd, lat, lon, alt)— local solar eclipseswe_set_sid_mode(mode)/swe_get_ayanamsa_ut(jd)— sidereal zodiacrise_trans(jd, body, lat, lon, rsmi=...)— sunrise and sunsetazalt(jd, flag, geopos, press, temp, xin)— horizontal coordinatesswe_pheno_ut(jd, body, flag)— phenomena (magnitude, phase, elongation)set_topo(lon, lat, alt)— observer’s position