xalen
Pure-Rust astronomical & astrology ephemeris for Python — VSOP87 planets, Vedic/Western/KP, house systems, ayanamsa, fixed stars, numerology. A drop-in for pyswisseph, with no AGPL and no data files to ship.
Part of the XALEN Ephemeris suite — pure-Rust core, zero
unsafein the math, thread-safe, Apache-2.0. The Python wheel is thexalen-pythoncrate (PyO3) wrapping that core.
Install
pip install xalen
To build from the repository instead:
pip install maturin
git clone https://github.com/vedika-io/xalen-ephemeris.git
cd xalen-ephemeris/crates/xalen-python
maturin develop --release # builds + installs into the active venv
# or build a wheel: maturin build --release -> target/wheels/xalen-*.whl
maturin enables the extension-module Cargo feature automatically (it is set in pyproject.toml), so the wheel resolves Python symbols against the host interpreter — no libpython link needed.
Requires Python ≥ 3.8.
Platform support
Wheels are built with the abi3 stable ABI (abi3-py38), so a single wheel
per platform imports unchanged on CPython 3.8 and every later 3.x — there is no
per-interpreter-version wheel to pick.
| Platform | On PyPI |
|---|---|
| Linux x86_64, glibc ≥ 2.17 (manylinux2014) | cp38-abi3 wheel |
| Linux aarch64, glibc ≥ 2.17 (manylinux2014) | cp38-abi3 wheel |
| macOS arm64, 11.0 or later | cp38-abi3 wheel |
| macOS x86_64, 10.12 or later | cp38-abi3 wheel |
| Windows, musl Linux and any other platform | sdist: pip install builds from source (needs a Rust toolchain) |
The wheels and the sdist are built on one Linux host by
scripts/build-release-artifacts.sh,
which also smoke-tests them. A Windows wheel is not published yet.
import xalen.swe as swe — the pyswisseph drop-in
The headline feature: migrating an existing pyswisseph (a.k.a. swisseph) codebase is a one-line search-and-replace.
# before
import swisseph as swe
# after
import xalen.swe as swe
Every function mirrors the shape of the matching pyswisseph entry point — argument order, tuple layout, and the SE_* / SEFLG_* / SIDM_* constants (exposed under both the bare pyswisseph spellings and the SE_-prefixed ones).
import xalen.swe as swe
jd = swe.julday(1990, 6, 15, 10.5) # -> Julian Day (UT1)
# Sun position with speed. Returns ((lon, lat, dist, lon_speed, lat_speed,
# dist_speed), ret_flag) — exactly like pyswisseph.
xx, retflag = swe.calc_ut(jd, swe.SUN, swe.FLG_SWIEPH | swe.FLG_SPEED)
lon, lat, dist, lon_speed, *_ = xx
# Sidereal (Lahiri) longitude.
swe.set_sid_mode(swe.SIDM_LAHIRI, 0.0, 0.0)
sid, _ = swe.calc_ut(jd, swe.SUN, swe.FLG_SWIEPH | swe.FLG_SIDEREAL)
# Houses: (cusps[12], ascmc[8]). ascmc = (asc, mc, armc, vertex,
# equatorial_ascendant, co_ascendant_koch, co_ascendant_munkasey, polar_ascendant).
cusps, ascmc = swe.houses_ex(jd, 18.52, 73.85, b"P") # Placidus (hsys as bytes)
asc, mc = ascmc[0], ascmc[1]
# Sidereal houses: pass SEFLG_SIDEREAL (active-mode ayanamsa subtracted).
sid_cusps, sid_ascmc = swe.houses_ex(jd, 18.52, 73.85, b"P", swe.FLG_SIDEREAL)
# Ayanamsa, ΔT, calendar round-trip, fixed stars.
aya = swe.get_ayanamsa_ut(jd)
dt = swe.deltat(jd)
y, m, d, h = swe.revjul(jd)
star_xx, star_name, _ = swe.fixstar2_ut("Aldebaran", jd, swe.FLG_SWIEPH)
mag, _ = swe.fixstar2_mag("Aldebaran")
Honest compatibility scope
XALEN is a faithful shape drop-in, not a byte-for-byte clone. The differences are deliberate and documented:
- Speeds (
xx[3..6]) are0.0unlessSEFLG_SPEEDis in the flags — matching Swiss. houses_exreturnsascmcof length 8; all eight slots are populated —[0..4]areasc, mc, armc, vertexand[4..8]are the equatorial ascendant, the Koch and Munkasey co-ascendants, and the polar ascendant (validated against pyswisseph'sswe.houses_armcauxiliary points).hsysmay be passed as bytes (b"P", the pyswisseph form) or str ("P"). PassingSEFLG_SIDEREALinflagsreturns the sidereal frame (active-mode ayanamsa subtracted from every cusp and angle; ARMC, a sidereal-time angle, is unchanged).swe.calc(jd_et, ...)takes a TT/ET epoch (like pyswisseph) and converts it to UT1 via ΔT before computing, soswe.calc(jd_tt, ...)equalsswe.calc_ut(jd_tt − deltat(jd_tt), ...). (The compat core still works in UT1;calcdoes the ΔT bridge for you.)- Position-altering flags XALEN does not yet implement (
SEFLG_HELCTR,SEFLG_TOPOCTR,SEFLG_J2000,SEFLG_EQUATORIAL,SEFLG_BARYCTR,SEFLG_XYZ,SEFLG_RADIANS) raiseValueErrorrather than silently returning a geocentric ecliptic position mislabeled as something else. A loud error beats a silent-wrong drop-in. set_ephe_path()andclose()are no-ops — XALEN embeds all data at compile time, so there are no ephemeris files to point at or resources to release.
See docs/MIGRATING_FROM_SWISS_EPH.md for the full migration guide.
Native xalen API (richer, structured returns)
When you are not porting Swiss code, the native xalen module gives you dicts instead of positional tuples.
planet_position — the full 6-tuple + retrograde
The high-fidelity counterpart to planet_longitude. Equivalent to Swiss calc_ut(..., FLG_SPEED) plus a retrograde flag:
import xalen
jd = xalen.julian_day(2000, 1, 1, 12.0) # J2000.0
p = xalen.planet_position(jd, 0) # body 0 = Sun, tropical
# {
# "longitude": 280.37, "latitude": 0.0, "distance": 0.9833, # AU
# "lon_speed": 1.0194, "lat_speed": ..., "dist_speed": ..., # per day
# "is_retrograde": False,
# }
# Sidereal (Lahiri = ayanamsa 0): sidereal longitude AND the ayanamsa's own
# precession rate is removed from lon_speed (matches Swiss SIDEREAL|SPEED).
p_sid = xalen.planet_position(jd, 0, sidereal=True, ayanamsa=0)
# Ketu (id 13) = Rahu + 180°, sharing Rahu's speed/retrograde.
ketu = xalen.planet_position(jd, 13)
longitude, latitude, and the speeds are in degrees (speeds per day); distance/dist_speed are in AU. longitude is wrapped to [0, 360); is_retrograde is taken from the tropical longitude rate regardless of the sidereal flag.
Body IDs
0=Sun · 1=Moon · 2=Mercury · 3=Venus · 4=Mars · 5=Jupiter · 6=Saturn · 7=Uranus · 8=Neptune · 9=MeanNode (Rahu) · 10=TrueNode · 11=Pluto · 12=Chiron · 13=Ketu (Rahu+180).
Other native functions
import xalen
jd = xalen.julian_day(1990, 6, 15, 10.5)
xalen.planet_longitude(jd, 1, sidereal=True, ayanamsa=0) # just the longitude (float)
xalen.all_planets(jd, sidereal=True, ayanamsa=0) # {"Sun":..,"Moon":.., "Ketu":..}
# Full Vedic chart: 9 grahas (+Ketu) with nakshatra/pada/rashi/lord, plus
# Whole-Sign ascendant, MC, ayanamsa_deg, and the 12 cusps.
chart = xalen.full_chart(jd, 18.52, 73.85, ayanamsa=0)
print(chart["planets"]["Sun"]) # {"longitude":.., "nakshatra":.., "pada":.., "rashi":.., "lord":..}
print(chart["ascendant"], chart["mc"], chart["ayanamsa_deg"])
# Panchang (five limbs) for a JD.
pan = xalen.panchang(jd, ayanamsa=0)
# {"tithi": {"number":..,"name":..,"paksha":"Shukla"|"Krishna"},
# "nakshatra": "...", "yoga": {"number":..,"name":..}, "karana": "...", "vara": "..."}
# Nakshatra detail from a sidereal Moon longitude.
nak = xalen.nakshatra(123.45) # {"name":.., "pada":.., "lord":.., "deity":.., "index":..}
xalen.rashi(123.45) # e.g. "Simha (Leo)"
# Houses across 14 systems (0=WholeSign .. 13=Krusinski).
h = xalen.houses(jd, 18.52, 73.85, system=2) # Placidus
# {"cusps":[12], "ascendant":.., "mc":.., "ic":.., "descendant":.., "vertex":..}
# Ayanamsa value (17 systems, 0=Lahiri).
xalen.ayanamsa(jd, system=0)
xalen.delta_t(jd) # ΔT (TT−UT1) seconds, SMH 2016 model
xalen.fixed_star_conjunctions(123.45, 1.0, 2000.0)
# Numerology.
xalen.life_path(1990, 6, 15)
xalen.expression_number("Ada Lovelace", "pythagorean") # or "chaldean"
# String-named convenience variants.
xalen.planet_longitude_by_name("moon", jd)
xalen.sidereal_longitude("moon", jd, "lahiri")
xalen.houses_by_name(jd, 18.52, 73.85, "placidus")
xalen.ayanamsa_by_name(jd, "lahiri")
Accepted name strings: bodies sun, moon, mercury, venus, earth, mars, jupiter, saturn, uranus, neptune, pluto, rahu/mean_node, true_node, chiron, lilith/mean_apogee. Ayanamsas lahiri, kp, raman, fagan-bradley, true-chitra, true-revati, surya-siddhanta, yukteswar, jn-bhasin, deluce, ushashashi, pushya-paksha, galactic-center, lahiri-icrc, kp-straight-line, hipparchos, lahiri-vp285. House systems whole-sign, equal, placidus, koch, porphyry, regiomontanus, campanus, morinus, alcabitius, topocentric, meridian, vehlow, sripati, krusinski-pisa (dashes/spaces ignored, case-insensitive).
Accuracy — what is and is not claimed
XALEN is cross-validated against JPL Horizons (DE440), the real DE440 binary kernel, the official VSOP87 check file, Swiss Ephemeris (swetest), and public calculators. The honest framing: XALEN matches these references to the bounds below — DE440 is the reference; we do not "beat" it. The differentiator is pure Rust, zero unsafe core, thread-safe, Apache-2.0, no data files.
| Body | Theory | Measured error vs JPL DE440 (analytical engine) |
|---|---|---|
| Sun, Mercury–Saturn | VSOP87A + IAU 2000B nutation | sub-arcsecond (Sun 0.21″, Mercury–Saturn ≤ 0.76″; 20k-chart bound) |
| Uranus, Neptune | VSOP87A + nutation | Uranus 1.78″, Neptune 2.53″ (20k-chart bound) |
| Moon | ELP2000-82 (Meeus, 60+60 terms) | ~3″ RMS (max ~12″), AD 1600–2100 |
| Pluto | Meeus Goffin 43-term fit | ~1 arcminute, 1885–2099 |
| Rahu/Ketu (Mean Node) | Analytical mean model | exact (mean model) |
vs Swiss Ephemeris: 0 of 5,000,000 charts over 0.1° for any planet or node (most < 0.02°), worldwide, 1850–2150. Loading the optional de440s.bsp kernel gives JPL-grade sub-arcsecond on the Sun + planets and full-range Pluto. Full report: docs/ACCURACY.md.
Which Swiss flags are / are not supported
| Supported (shape + behavior) | Not supported — raises ValueError |
|---|---|
SEFLG_SWIEPH (always the active backend) |
SEFLG_HELCTR (heliocentric) |
SEFLG_SPEED (daily motion) |
SEFLG_TOPOCTR (topocentric) |
SEFLG_SIDEREAL (with set_sid_mode) |
SEFLG_J2000 (J2000 frame) |
SEFLG_EQUATORIAL (RA/Dec) |
|
SEFLG_BARYCTR (barycentric) |
|
SEFLG_XYZ (cartesian) |
|
SEFLG_RADIANS |
All these constants are still defined on xalen.swe (so existing import lines and flags = ... arithmetic work); only their position-altering effect is unimplemented, and passing one to calc_ut raises rather than silently returning the wrong frame.
Editor autocomplete
Type stubs ship with the wheel (xalen.pyi, xalen/swe.pyi), so pyswisseph and native users both get full autocomplete and type checking in editors and mypy.
License
Apache-2.0. See LICENSE and CREDITS.md.
Metadata
Release files for xalen 0.7.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| xalen-0.7.0.tar.gz | 1.1 MB | Without provenance | Details for xalen-0.7.0.tar.gz |
Built distributions (wheels)
| File | Actions Reset | |||
|---|---|---|---|---|
| xalen-0.7.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl | CPython 3.8 | abi3 | Linux glibc 2.17+ x86-64 | Details for xalen-0.7.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl |
| xalen-0.7.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl | CPython 3.8 | abi3 | Linux glibc 2.17+ ARM64 | Details for xalen-0.7.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl |
| xalen-0.7.0-cp38-abi3-macosx_11_0_arm64.whl | CPython 3.8 | abi3 | macOS 11.0+ ARM64 | Details for xalen-0.7.0-cp38-abi3-macosx_11_0_arm64.whl |
| xalen-0.7.0-cp38-abi3-macosx_10_12_x86_64.whl | CPython 3.8 | abi3 | macOS 10.12+ x86-64 | Details for xalen-0.7.0-cp38-abi3-macosx_10_12_x86_64.whl |
Total release size: 7.0 MB
Release files / xalen-0.7.0.tar.gz
| Download URL | xalen-0.7.0.tar.gz |
|---|---|
| Size | 1.1 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
50c866204bd4707fe26f4d053028049452e4765565f7a96a255b8cff581ecc48
|
|
BLAKE2b-256 checksum How to use checksums |
8d581221e4ca9b965dd1ed9ea87b39b4e412af09fb7a74ad7e7580746cbed288
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.10
|
Release files / xalen-0.7.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
| Download URL | xalen-0.7.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl |
|---|---|
| Size | 1.5 MB |
| Tags | CPython 3.8 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
d0916ff5c19f70bef2b6ed46acc125cc41830795725659f0a05715134ea430aa
|
|
BLAKE2b-256 checksum How to use checksums |
8b072bad30ac87146def06b47595eeeb56d62e9ac35e21d74c0f92b31edfdf7b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.10
|
Release files / xalen-0.7.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
| Download URL | xalen-0.7.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl |
|---|---|
| Size | 1.4 MB |
| Tags | CPython 3.8 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
1e511799384cd1f19c9d084eb4adae0cb8aff7d76fa3382fa2b9362b3f0e99dc
|
|
BLAKE2b-256 checksum How to use checksums |
eeff8f3e91eb88bf99e5e27b1b18bfeb468c711ea8fe36afeb57388cb52f8dc3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.10
|
Release files / xalen-0.7.0-cp38-abi3-macosx_11_0_arm64.whl
| Download URL | xalen-0.7.0-cp38-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 1.5 MB |
| Tags | CPython 3.8 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
6392853392fd0473be9c968e72aed8d398287e6a838ad2ccb3b0481b7f7f57af
|
|
BLAKE2b-256 checksum How to use checksums |
b0e8225058b391be62dc1952ac58ea13f728a5b9b5a93cac2402fc7e47a21fc4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.10
|
Release files / xalen-0.7.0-cp38-abi3-macosx_10_12_x86_64.whl
| Download URL | xalen-0.7.0-cp38-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 1.5 MB |
| Tags | CPython 3.8 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
8540caf1c7545b2aa8bd00e6e3b6cdedb8a735bdc43a37496af3adb462e38d7d
|
|
BLAKE2b-256 checksum How to use checksums |
4cd77d4fc2e0f6b4922315ec6941162f3686846b27fb8666aafbec7befec374f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.10
|
Security for xalen 0.7.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.