← Blog

Release · Correctness · Performance

Vedaksha v5.0.0 — three defects that moved every chart

Unlike v4.0.0, this release changes numerical results. If you store computed charts, they will not match a recomputation under v5. It is also 26.2x faster on panchanga.

August 16, 2026·6 min read·ArthIQ Labs

Most releases add surface. This one removes error. Three long-standing defects are corrected, and all three produced answers that looked entirely reasonable.

What this release is, and is not

v5 corrects answers that were wrong. It does not make the ephemeris more accurate. Residuals against the JPL Horizons oracle are identical to v4 — mean 0.880″ overall, 0.106″ in the measured-delta-T era — and the 24,350-row SPK oracle digest is byte-identical.

If you were reading planetary positions, v5 gives you the same numbers. If you were reading ascendants, houses or the vara, it gives you correct ones for the first time.

1. The ascendant was built on the wrong clock

An ephemeris runs on two clocks. Planetary positions are a function of dynamical time. The orientation of the Earth under those planets is a function of UT1, which tracks the actual rotation. The gap between them, delta-T, is about 69 seconds today.

Sidereal time measures Earth's rotation, so the right ascension of the midheaven is defined on UT1. The engine converted to Terrestrial Time first — which does not merely ignore delta-T, it applies that rotation in the wrong direction, adding it where it should have been removed.

The RAMC error is uniform, about 0.29 degrees today, and it grows with delta-T. The resulting ascendant error is not uniform, because it varies with latitude: measured between 5.18 and 30.23 arcminutes across three test charts. Every ascendant, MC and house cusp the engine has ever returned carries it. No planetary longitude was affected.

2. Sidereal charts mixed two coordinate frames

This is the one that moved the most output. compute_chart rotated the planets by the ayanamsha and left the house cusps tropical — then placed the rotated planets against the unrotated cusps.

With Lahiri, an ayanamsha of roughly 24.2 degrees, that is 0.81 of a house. 77.4% of placements — 669 of 864 measured — fell in the wrong bhava.

The default Tropical configuration was unaffected, which is precisely why the defect survived as long as it did: the path everyone tested first was the path that was right. Whole-Sign is now re-anchored on the rotated ascendant rather than rigidly rotated, because its cusps genuinely are sign boundaries and move with the frame.

3. A vara is not a weekday

weekday_from_jd returned the Universal Time calendrical weekday. A vara runs from local sunrise to local sunrise, so it depends on where the observer is standing. The UT day boundary lands at a different local clock time at every longitude, so between that boundary and local midnight the engine reported the following day's weekday — and its day lord.

Worse, the signature took only jd. There was no parameter through which a caller could supply a latitude or longitude, so every consumer received the UT answer and had no way to correct it. It reached all four shipped surfaces.

The engine could not have computed the right answer, because it had no sunrise routine at all. v5 adds one and derives the vara from it. Rahu Kalam and Gulika Kalam became real (start_jd, end_jd) windows rather than a slot index you could not place on a clock. Gulika Kalam had been computed and tested but was reachable from no shipped surface at all, while the README advertised it.

Above 89 degrees latitude a sunrise can be attributed to the wrong rotation, so that band is routed through a retained brute-force scanner. Polar day and night return no instant rather than a fabricated one, and a new vara.from_sunrise flag distinguishes a genuine sunrise-reckoned vara from the civil fallback. That substitution used to be silent.

Error removed, by quantity

RAMC (sidereal time argument)~0.29 deg today, uniform - grows with delta-T
Ascendant, MC, house cusps5.18' to 30.23', varying with latitude
Sidereal house placement77.4% corrected (669 of 864 measured)
Varaup to a full day's label, at every longitude between the UT boundary and local midnight

Latency

Fixing sunrise properly also made it fast. The old code bracketed a horizon crossing by scanning the day at five-minute resolution — roughly 660 ephemeris evaluations to locate one event. Rise, set and transit are now solved analytically from Meeus, Astronomical Algorithms, Ch.15, iterating on the local hour angle the body must reach. That is about 25 evaluations.

Operationv4v5Gain
compute_panchanga203.5 ms7.75 ms26.2x
Muhurta scan, 365-day3,754.1 ms458.2 ms8.19x
8 concurrent requests1,180 ms203 ms5.80x
search_transits450.06 ms151.77 ms2.97x
compute_transit51.52 ms25.30 ms2.04x
compute_natal_chart25.81 ms12.98 ms1.99x
Served muhurta request1,483.0 ms785.6 ms1.89x

The chart and transit gains come from computing same-instant body sets through one batched call, which hoists the per-timestamp nutation and precession frames instead of rebuilding them per body. The muhurta gains come from evaluating each body once per instant instead of up to four times, and from scanning candidate days across threads. The concurrency number is the MCP HTTP transport moving from a serial accept loop to a bounded worker pool — it previously handled exactly one request at a time, and since an MCP call here is a real ephemeris solve rather than a lookup, one long transit search blocked every other client for its duration.

Every one of these is bit-identical to the previous implementation, verified by comparing served JSON byte for byte and by an agreement sweep against the retained brute-force scanner whose worst disagreement was one ULP.

What breaks

compute_panchanga now requires latitude and longitude. A vara without an observer is meaningless.

weekday_from_jd is renamed ut_weekday_from_jd and documented as the UT calendrical weekday, explicitly not a vara. The old name is what invited the misuse.

assess_muhurta takes the vara; search_muhurta takes the observer. No compatibility shim was added on purpose — a shim would have defaulted to the UT weekday, which is the defect being removed. A compile error beats a silently wrong vara.

julian_day is documented as UT1, not TDB. Six tool schemas said TDB while the code treated the value as UT1, so a caller obeying the published schema was supplying the wrong time scale.

Minimum supported Rust version moves from 1.85 to 1.89.

A note for Python users

Worth restating, because it is the part people assume must have a catch: pip install vedaksha installs the real engine. Not a port, not a reimplementation and not a binding you compile. The Rust engine is built to WebAssembly and hosted from Python through wasmtime over a raw C ABI.

So there is no build step anywhere. One py3-none-any wheel covers every OS and CPU wasmtime supports, on Python 3.9 and up. No Rust toolchain on your machine or in your CI, no C extension, no manylinux wheel matrix, no compiler in the Docker image.

And the results are the same results: a conformance suite checks the WASM output against a fixture generated from the native build using exact equality rather than a tolerance, asserting the natal chart values bit for bit. Every correction above lands on the Python surface at the same time and with the same numbers.

One caveat, and where the numbers come from

The wide SIMD dependency moved from 0.7.33 to 1.6.1 because its 0.7 line is end-of-life, and that upgrade is notbit-identical in the lunar theory: 6 of 21,915 analytical-oracle rows differ, all of them the Moon, at most 1 ULP in longitude. That is a dependency's rounding rather than an accuracy change and it moves no published figure — but a bit-reproducibility claim should not quietly stop being true, so it is stated here and pinned by a digest test.

The residual figures come from the committed 24,350-row Horizons fixture and are reproducible from a checkout. The ascendant-shift range, the 669-of-864 placement count and the served-latency numbers are development measurements taken against the running HTTP surface on aarch64 in release — they are reported, not artifacts you can regenerate from the repository. We would rather label which is which.

Upgrade: cargo add vedaksha · pip install -U vedaksha · Read the docs: /docs

Install 5.0.2. 5.0.0 and 5.0.1 are yanked on crates.io — 5.0.1 published to PyPI, npm and Docker but died partway through the crates.io publish order, and 5.0.2 is behaviourally identical and exists to ship a complete set.