Known Divergences from pyswisseph #
This document catalogs all known, inherent divergences between libephemeris and pyswisseph 2.10.03. These are not bugs — they arise from fundamental differences in the underlying computation engines (Skyfield/JPL DE440 vs Swiss Ephemeris) and different implementations of secondary algorithms.
All divergences documented here have been verified through systematic hyper-validation (4400+ comparison rounds across 29 API sections).
Summary #
| Category | Typical Divergence | Maximum Divergence | Cause |
|---|---|---|---|
| Planetary positions | 0.001–0.5" | ~1" (Moon) | Different ephemeris engines |
| Planetary speeds | 0.01–5" | ~20" (Moon speed) | Numerical differentiation methods |
| House cusps | <0.01" | ~0.01" | Obliquity/nutation model differences |
| Fixed star positions | <0.01" | <0.01" | Proper motion catalog differences |
| Fixed star distances | <0.01% at J2000 | ~0.1% at ±50y | Radial velocity models |
| Ayanamsha values | <0.1" | ~40" (exotic modes) | Reference star position differences |
| Delta-T | <0.001s (modern) | ~43s (year 1900) | Different delta-T polynomial models |
| Refraction | <1" | ~15" | Different atmospheric models |
| Phase angles | <1" (inner) | ~18" (outer planets) | Position errors amplified |
| Orbital elements | <1" (inner) | ~2000" (Jupiter) | Different osculating element derivation |
| Sidereal Moon | ~3" | ~14" | Lunar theory differences in sidereal frame |
1. Ephemeris Engine Differences #
1.1 Planetary Positions (swe_calc_ut) #
libephemeris uses Skyfield + JPL DE440/DE441 while pyswisseph uses the Swiss Ephemeris internal integration. Both are based on JPL ephemerides but use different interpolation and integration methods.
Typical divergence:
- Sun, Mercury–Neptune: 0.001–0.01" (sub-arcsecond)
- Moon: 0.01–1.0" (lunar theory differences)
- Pluto: 0.001–0.01"
Speed divergence:
- Most planets: 0.01–2.0"
- Moon speed: up to ~5" (different numerical differentiation)
- With
SEFLG_SPEEDflag: central finite difference vs analytical
Future dates (>2050): Up to 2" position divergence due to delta-T model extrapolation differences.
1.2 Interpolated Apogee/Perigee (bodies 21, 22) #
IntpApog and IntpPerg use semi-analytical ELP2000-82B perturbation theory. libephemeris implements this independently from published coefficients, producing results that can differ by several arcseconds from the Swiss Ephemeris implementation. These bodies are classified as KNOWN divergence in all tests.
1.3 Pholus (body 16) Historical Dates #
Pholus SPK coverage in libephemeris starts around 1600 CE. Queries before this date raise “Invalid Time to evaluate” while pyswisseph may use internal Keplerian fallback. This affects dates before ~1850.
2. House System Differences #
2.1 House Cusps #
House cusps typically agree within 0.01" (sub-arcsecond). The small divergence comes from slightly different obliquity and nutation models used in the intermediate calculations.
2.2 Vertex at the Equator #
At geographic latitude 0° (equator), the Vertex calculation has a 1/tan(lat)
singularity. libephemeris clamps latitude to a tiny epsilon so the formula
evaluates to the correct limiting value, matching pyswisseph. The only remaining
divergence is ascmc[6] (CoAsc Munkasey) for the Horizon (H) house system at
lat=0, where pyswisseph returns 0° due to a C tan(90°) floating-point
artifact, while the mathematical limit from any positive latitude is 180°.
2.3 House Position (swe_house_pos) #
For most house systems: <0.01" divergence.
For Alcabitius (B), Koch (K), and Topocentric (T): up to ~46" divergence due to different cusp interpolation algorithms between the engines.
3. Time and Delta-T #
3.1 Delta-T Model #
libephemeris uses Skyfield’s delta-T model (based on historical observations and IERS data) while pyswisseph uses the Espenak & Meeus (2006) polynomial model.
| Period | Typical Divergence | Maximum |
|---|---|---|
| 1950–2020 | <0.001s | ~0.01s |
| 1900–1950 | ~1s | ~43s |
| 2020–2050 | <0.1s | ~1s |
| >2050 | ~1–10s | depends on extrapolation |
This affects all functions that internally convert between UT and ET/TT:
swe_deltat, swe_deltat_ex, swe_utc_to_jd, swe_jdet_to_utc,
swe_jdut1_to_utc.
3.2 Sidereal Time (swe_sidtime) #
At modern dates: <0.001s divergence. At future dates (>2050): up to ~0.05s due to delta-T propagation into the GMST calculation.
3.3 Equation of Time (swe_time_equ) #
At modern dates: <0.15s (matches well after the GAST-RA rewrite). At future dates (>2050): up to ~1.4s due to compounding delta-T and Sun RA model differences.
4. Fixed Stars #
4.1 Positions #
Ecliptic longitude and latitude agree within 0.01" for all 116 catalog stars. This is achieved by using the same FK5/Hipparcos proper motion values.
4.2 Distances #
At J2000.0 epoch: distances match exactly (same Hipparcos parallax values). At other dates: up to ~0.1% divergence due to different radial velocity models affecting the distance variation over time.
4.3 Speed in Distance (speed_dist) #
speed_dist (index 5 of the position tuple) diverges significantly (20–90%)
because libephemeris computes it via central finite difference on the full
apparent distance (which includes annual parallax oscillation), while pyswisseph
separates the radial velocity component differently.
4.4 Magnitudes #
Star magnitudes agree within 0.5 mag for all catalog stars. Small differences arise from different catalog versions.
5. Refraction and Horizontal Coordinates #
5.1 Atmospheric Refraction (swe_refrac) #
Up to 15" divergence, primarily at low altitudes near the horizon. Both implementations use Bennett’s formula but with slightly different coefficients and boundary handling.
5.2 Azimuth/Altitude (swe_azalt) #
Above-horizon bodies: typically <1" divergence. Below-horizon bodies: up to ~1654" (~27’) divergence due to fundamentally different atmospheric refraction models for negative apparent altitudes. This is a known limitation — refraction below the horizon is physically meaningless and implementations differ in how they extrapolate.
6. Eclipse and Occultation Functions #
6.1 Solar Eclipses #
Eclipse timing (tret[0] maximum): typically <10s divergence.
Eclipse geography (geopos): typically <1° divergence.
Eclipse type flags may occasionally differ for borderline cases.
6.2 Lunar Eclipses #
Similar to solar eclipses. Timing agrees within ~10s for most events.
6.3 Lunar Occultations #
Timing agrees within ~0.001 day (~86s) for most events.
Note: swe_lun_occult_when_glob is computationally expensive (~6s/call).
7. Nodes and Apsides (swe_nod_aps_ut) #
Nodal and apsidal positions can diverge by 20–700" (up to ~1°) for some bodies. This is because osculating orbital elements are derived differently from the two ephemeris engines. The divergence is largest for:
- Outer planets (different integration methods)
- Bodies with high eccentricity (osculating elements more sensitive)
8. Phenomena (swe_pheno_ut) #
8.1 Phase Angle #
Inner planets (Sun–Mars): <1" divergence. Outer planets (Jupiter–Pluto): up to ~18" divergence. The phase angle calculation amplifies the small position differences between the two engines.
8.2 Other Phenomena Values #
Phase, elongation, apparent diameter, and magnitude generally agree well (<1").
9. Orbital Elements (swe_get_orbital_elements) #
Orbital elements for inner planets (Mercury–Mars) agree within ~1".
Orbital elements for outer planets (Jupiter–Neptune) can diverge by 100–2000" in angular elements (argument of perihelion, longitude of ascending node, mean anomaly). This is because osculating elements are derived from instantaneous position and velocity vectors, and small position differences between the engines lead to large differences in the derived elements, especially for nearly circular orbits where the argument of perihelion is poorly defined.
Fictitious bodies (IntpApog=15, IntpPerg=16) have meaningless orbital elements since they are not real orbiting bodies.
10. Sidereal Calculations #
10.1 Sidereal Positions #
Most planets in sidereal mode agree within 1" (the ayanamsha subtraction is consistent).
Sidereal Moon: 3–14" divergence. The sidereal frame amplifies the underlying lunar theory differences because the ayanamsha correction interacts with nutation differently in the two engines.
10.2 Ayanamsha Values #
Standard modes (Lahiri, Fagan-Bradley, Raman): <0.1" divergence. Exotic/experimental modes: up to ~40" divergence for modes that depend on specific reference star positions or galactic frame definitions.
11. Crossing Functions #
11.1 Solar/Lunar Crossings #
swe_solcross_ut, swe_mooncross_ut: typically <1s timing divergence.
11.2 Moon Node Crossings #
swe_mooncross_node_ut: up to ~69s divergence due to different lunar
node calculation methods.
12. Asteroid Pipeline (SE_AST_OFFSET) #
When using SE_AST_OFFSET + N to access asteroids by number, libephemeris
remaps to dedicated body IDs and uses its Skyfield/SPK pipeline. pyswisseph
uses .se1 ephemeris files with a different integration.
Typical divergence: ~0.2" for the major asteroids (Ceres, Pallas, Juno, Vesta).
Missing .se1 files: Chiron (2060) and Pholus (5145) require dedicated
.se1 files in pyswisseph’s ephemeris path. If these files are not present,
pyswisseph raises an error. libephemeris always has these bodies available
through its SPK pipeline.
13. Heliacal Events #
swe_heliacal_ut is computationally expensive (>90s per call for some
configurations). Timing divergence: up to ~2 days for heliacal rising/setting
events, due to different atmospheric extinction and visibility models.
14. Constants and API #
14.1 Version String #
version intentionally differs (libephemeris reports its own version).
14.2 contrib Attribute #
pyswisseph exposes a contrib attribute (contributor credits). libephemeris
does not expose this attribute.
14.3 d2l with Negative Values #
swe_d2l() for negative input values produces different results due to
unsigned integer overflow behavior in the C implementation of pyswisseph
vs Python’s native integer handling.
14.4 SEFLG_MOSEPH #
SEFLG_MOSEPH is accepted for API compatibility but silently ignored. All
calculations always use JPL DE440/DE441 via Skyfield — there is no Moshier
ephemeris fallback.
15. Gauquelin Sectors #
Gauquelin sector values typically agree within 0.01 sectors. Small divergences (<0.1) arise from the underlying planetary position and house cusp differences.
Hyper-Validation Results #
The hyper-validation script (scripts/hyper_validate.py) runs 4400+ comparison
rounds across 29 sections. With all tolerance classifications applied:
| Metric | Count | Percentage |
|---|---|---|
| PASS | 3947 | 89.7% |
| KNOWN | 441 | 10.0% |
| FAIL | 0 | 0.0% |
| SKIP | 12 | 0.3% |
0 failures. All divergences are documented and classified as inherent differences between the two computation engines.
The 12 SKIP results are due to missing .se1 asteroid ephemeris files in the
pyswisseph configuration (not a libephemeris limitation).
Run the hyper-validation yourself:
.venv/bin/python3 scripts/hyper_validate.py --json report.json
Exclude slow sections (occultations, heliacal) with:
.venv/bin/python3 scripts/hyper_validate.py --section A,B,C,D,E,F,G,H,I,K,L,M,N,O,P,Q,R,S,T,V,W,X,Y,Z,AA,AB,AC