Getting started
Install
PyPI (default)
Alkahest is on the Python Package Index. Supported interpreters are Python 3.10 through 3.14 (requires-python on PyPI).
python -m pip install -U pip
pip install alkahestUse a virtual environment when you also build from source or test multiple Python versions:
python3 -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
python -m pip install -U pip
pip install alkahestDefault PyPI wheels are built with egraph, groebner, cranelift and parallel: the
vendored egglog e-graph backend, the Gröbner solver (so alkahest.solve,
Diophantine and homotopy work out of the box), the pure-Rust Cranelift JIT (so
alkahest.jit_is_available() is True without a system LLVM), and the Rayon-backed
multi-core paths. They do not include the LLVM JIT (llvm_jit) or cuda.
Wheels exist for Linux (manylinux_2_28) x86_64 and aarch64, macOS 11+ arm64 (no Homebrew needed; FLINT, GMP and MPFR are bundled) and Windows x86_64.
macOS support is Apple Silicon only (macOS 11+). Intel Macs (x86_64) are not
supported: no wheel is built for them, and a source build for x86_64-apple-darwin
stops at once with "alkahest supports macOS on Apple Silicon only; Intel Macs are not
supported".
On any other platform without a wheel, such as musl Linux, pip install alkahest falls
back to the source distribution and compiles it. That needs a Rust toolchain and
FLINT ≥ 3.3 with GMP and MPFR; without FLINT the build stops and prints how to install
it. A build from the source distribution has the default Cargo features, egraph and
groebner: no JIT and no parallel. For those, build from a checkout
with --features.
parallelis on in the default wheel — but still check it
capabilities()["features"]["parallel"]isTrueon every wheel published to PyPI, on Linux, macOS and Windows alike. TheparallelCargo feature is what powers the shardedExprPooland every*_parentry point (numpy_eval_par,simplify_par), so those are genuinely multi-core out of the box. It also runs the primes and the verification of the multimodular Gröbner engine in parallel.It is still the one feature worth probing rather than assuming, because its absence is silent.
parallelis not a Cargo default: a source build that does not pass--features parallelstill gets workingnumpy_eval_parandsimplify_par— they transparently fall back to their single-threaded counterparts, with no error, no warning, and no speedup. Benchmarkingnumpy_eval_paragainstnumpy_evalon such a build measures the same number twice.Never infer parallelism from the function existing; ask:
Pythonimport alkahest as ak if not ak.capabilities()["features"]["parallel"]: ... # a source build without --features parallel; *_par is a no-op alias here
There is no pip install alkahest[jit] / alkahest[full] that swaps the native extension: pip extras only add Python dependencies, not alternate binaries.
For native LLVM CPU JIT use an opt-in +jit or +full Linux wheel from GitHub Releases (below), or build from source with --features jit. See the repository README.md for the same policy in short form.
Optional Linux wheels (+jit / +full)
Tagged releases attach linux_x86_64 wheels on GitHub Releases (CI builds them on ubuntu-22.04; these are not the manylinux wheels published as the default PyPI binaries). Pick the .whl whose tags match your Python (cp311, etc.) and linux_x86_64.
| Local version | Cargo features | When to use |
|---|---|---|
+jit |
egraph groebner jit parallel |
LLVM CPU JIT in place of Cranelift; everything else matches the default PyPI wheel. |
+full |
egraph groebner jit cranelift parallel |
The only wheel with both JIT backends, and a strict superset of the default wheel. Use it when you want LLVM without giving up Cranelift. |
Example direct installs (replace <version> and the wheel name using the release asset list):
pip install "https://github.com/alkahest-cas/alkahest/releases/download/v<version>/alkahest-<version>+full-cp311-cp311-linux_x86_64.whl"
pip install "https://github.com/alkahest-cas/alkahest/releases/download/v<version>/alkahest-<version>+jit-cp311-cp311-linux_x86_64.whl"These wheels vendor LLVM and related .so files under site-packages/alkahest.libs/. If import alkahest fails with a missing libLLVM-*.so or libffi-*.so, prepend that directory to LD_LIBRARY_PATH (or install matching system packages).
If your downloader rejects + in the URL, percent-encode it in the filename segment (e.g. 2.0.2%2Bfull).
After +jit or +full, alkahest.jit_is_available() should be True. Gröbner-backed APIs such as alkahest.solve are available in all wheels (including the default PyPI wheel) since groebner became a default Cargo feature in 2.3.1.
macOS and Windows +jit / +full wheels are not produced in CI yet; use building from source there.
Roadmap: a small PEP 503 extras index URL hosting only +jit / +full wheels (PyTorch-style --extra-index-url). Until then, use PyPI for the default wheel or direct URLs / asset downloads from Releases.
Build-profile verification
Every published wheel runs an import-and-capability smoke test in release CI. After installing, inspect the exact native build rather than inferring features from available Python functions:
import alkahest as ak
features = ak.capabilities()["features"]
print(features)| Distribution | Tested platforms | Native feature profile | parallel |
|---|---|---|---|
| Default PyPI wheel | Linux x86_64 and aarch64, macOS arm64, Windows x86_64 | egraph, groebner, cranelift_jit, parallel |
True |
| PyPI source distribution | Linux x86_64 (built in CI); anywhere with Rust and FLINT ≥ 3.3, except Intel macOS | egraph, groebner |
False |
Release +jit |
Linux x86_64 | egraph, groebner, llvm_jit (no Cranelift), parallel |
True |
Release +full |
Linux x86_64 | same as +jit |
True |
| Source build | any | whatever you pass to --features |
True only with --features parallel |
parallel is called out separately because it is the one feature whose absence is
silent: numpy_eval_par and simplify_par exist and work in every build, and simply
stop being parallel when it is off. Every published wheel now enables it, so the row
that can surprise you is the last one — parallel is not a Cargo default, so a source
build has to ask for it.
jit and cranelift remain compatibility names in this mapping. Prefer
llvm_jit and cranelift_jit when selecting a backend explicitly. cuda
indicates that the extension was compiled with NVPTX codegen — it guarantees
that ak.compile_cuda and ak.CudaCompiledFn exist, but not that a usable GPU
is present at runtime, and it is in no published wheel. There is no
groebner_cuda bit: the GPU Gröbner kernel has no Python binding, so the bit
was unfalsifiable from Python and was removed in contract v3. Read
GPU support before branching on cuda.
Optional: RL environments (alkahest[rl])
Reinforcement-learning environments (symbolic integration, Prime Intellect Hub) are an optional extra.
pip install "alkahest[rl]"This adds verifiers and datasets. Environment code ships in the main wheel under
alkahest.rl. See the RL guide for API details, veRL integration, and
Environments Hub publishing.
From source
For optional Cargo features (jit, parallel, cuda, …), GPU/NVPTX, or development, build the PyO3 extension with maturin. The groebner and egraph features are default and included automatically.
Prerequisites (typical): Rust stable (≥ 1.76) and nightly, LLVM 21 (only for --features jit), FLINT (≥ 3.3, ≥ 3.4 for Riemann theta; pulls in GMP/MPFR — many distribution packages are older, in which case build FLINT from its release tarball into a prefix as shown below). The PyPI wheels are built with the opt-in system-gmp feature, which makes rug share FLINT's libgmp instead of compiling its own; to match them add system-gmp to --features, which needs GMP ≥ 6.3 / MPFR ≥ 4.2 (not Ubuntu 22.04 / Debian 12; on Homebrew, export CPATH/LIBRARY_PATH for the gmp and mpfr kegs). See the repository README for distro-specific package names.
FLINT is a hard requirement of every source build. There is no FLINT-free configuration:
UniPolyis a FLINT polynomial, and factorization, resultants, normal forms andnumber_theorycall FLINT directly with no pure-Rust fallback. Theflint3Cargo feature selects which FLINT version's API to use — it does not make FLINT optional. Without it the build stops early with an install hint (sudo apt-get install libflint-dev,dnf install flint-devel,brew install flint,conda install -c conda-forge libflint, …).Without root, build FLINT into a user-local prefix and point the build at it:
ShellFLINT_LIB_DIR=$PREFIX/lib FLINT_INCLUDE_DIR=$PREFIX/include \ maturin develop --manifest-path alkahest-py/Cargo.toml --release export LD_LIBRARY_PATH=$PREFIX/lib # DYLD_LIBRARY_PATH on macOSBoth variables also feed FLINT version detection, so a locally built FLINT 3 is recognised as FLINT 3.
ALKAHEST_SKIP_FLINT_CHECK=1bypasses the presence probe. If you only need a working install, the PyPI wheels already bundle FLINT.
pip install maturin
git clone https://github.com/alkahest-cas/alkahest.git
cd alkahest
maturin develop --manifest-path alkahest-py/Cargo.toml --releaseThe default build already includes egraph and groebner. Additional optional features:
# LLVM JIT for native compiled evaluation
maturin develop --manifest-path alkahest-py/Cargo.toml --release --features jit
# Pure-Rust Cranelift JIT (fast compile, no system LLVM required)
maturin develop --manifest-path alkahest-py/Cargo.toml --release --features cranelift
# Parallel simplification (sharded ExprPool + simplify_par + numpy_eval_par)
maturin develop --manifest-path alkahest-py/Cargo.toml --release --features parallel
# CUDA / NVPTX codegen (requires CUDA toolkit and LLVM with NVPTX target)
maturin develop --manifest-path alkahest-py/Cargo.toml --release --features cuda
# Full native build (JIT + parallel; egraph and groebner are already default)
maturin develop --manifest-path alkahest-py/Cargo.toml --release --features "parallel cranelift jit"Rust crate
alkahest-cas is also published on crates.io (docs.rs) for use directly from Rust:
[dependencies]
alkahest-cas = "3"
# groebner is included by default; add other optional features as needed:
# alkahest-cas = { version = "3", features = ["parallel", "egraph"] }System prerequisites (same libraries as the Python build — must be installed before cargo build):
# Debian / Ubuntu
sudo apt-get install -y libflint-dev libgmp-dev libmpfr-dev
# macOS (Homebrew)
brew install flintThe jit feature additionally requires LLVM 21 dev headers (llvm-21-dev / brew install llvm@21).
A self-contained runnable example is in examples/rust_quickstart/.
First steps
Every computation starts with an ExprPool. It owns all expressions; you create symbols and integers from it.
import alkahest
from alkahest import ExprPool, diff, simplify, integrate, sin, exp, cos
pool = ExprPool()
x = pool.symbol("x")
y = pool.symbol("y")Building expressions
Python operators build expression trees. Python numbers work on either side
of an operator and are coerced exactly — int to an integer, fractions.Fraction
to a rational; a float stays a float:
from fractions import Fraction
expr = x**2 + 2 * x + 1
print(expr) # x^2 + 2*x + 1
alt = (-1)**x * 2**x + Fraction(1, 3)
half_x = pool.rational(1, 2) ** x # base exactly 1/2, on every PythonA Fraction base depends on the Python version, because Python asks
Fraction.__pow__ first. On Python 3.12 and later it returns NotImplemented,
Alkahest receives the Fraction, and Fraction(1, 2) ** x has the exact base
1/2. On Python 3.10 and 3.11, Fraction.__pow__ returns float(a) ** b, so
Alkahest only ever sees the float 0.5 and Fraction(1, 2) ** x has the
float base 0.5, the same as 0.5 ** x. Write pool.rational(1, 2) ** x
for a base that is exact on every Python. (A Fraction exponent,
x ** Fraction(1, 3), is exact everywhere.)
Math functions accept expressions and Python numbers:
f = sin(x**2) + exp(x * y)
alkahest.sin(2), alkahest.sqrt(2), alkahest.exp(1) # exact arguments, no TypeErrorConstants, factoring and expanding
The SymPy spellings exist at the top level:
from alkahest import pi, E, I, oo, factor, expand, binomial, factorial
alkahest.simplify(sin(pi)).value # 0
factor(x**2 - y**2) # (x - y)*(x + y), an Expr product over ℤ
expand((x + 1)**3) # x^3 + 3*x^2 + 3*x + 1
binomial(10, 3), factorial(5) # 120, 120 (exact); symbolic n gives gamma ratios
alkahest.Matrix([[1, 2], [3, 4]]).det() # -2pi, E (Euler's number, exp(1)), I and oo are expressions in the pool of
the active alkahest.context(pool=...), or else in the process-wide
alkahest.default_pool() — the same pool sin(2), parse("x + 1") and
alkahest.symbol("x") use when no Expr argument names one. Because they have no
free symbols they mix into any pool: x + pi works for x from your own pool.
pool.symbol("pi") is an ordinary variable that prints as pi; it is not π
(simplify(sin(pool.symbol("pi"))) stays as it is). The constant is pi,
pool.pi() or parse("pi", pool).
Results pickle — DerivedResult, Expr and Matrix — so they can be sent through
multiprocessing.Pool.map and come back into the pool they were sent from.
Parsing expressions from strings
Use parse when the expression comes from user input or a config file:
from alkahest import parse
e = parse("x^2 + 2*x + 1", pool, {"x": x})
print(e) # x^2 + 2*x + 1Identifiers not in the symbols dict are auto-created as symbols in pool.
Both ^ and ** denote exponentiation. See Parsing from strings
for the full syntax reference.
Simplification
r = simplify(x + pool.integer(0))
print(r.value) # x
print(r.steps) # [RewriteStep(rule='add_zero', ...)]Differentiation
dr = diff(sin(x**2), x)
print(dr.value) # 2*x*cos(x^2)Integration
r = integrate(exp(x), x)
print(r.value) # exp(x)
r = integrate(sin(x), x)
print(r.value) # -cos(x)Polynomial arithmetic
from alkahest import UniPoly, RationalFunction
# Convert to FLINT-backed univariate polynomial
p = UniPoly.from_symbolic(x**3 + pool.integer(-1), x)
q = UniPoly.from_symbolic(x + pool.integer(-1), x)
print(p.gcd(q)) # x - 1
print(p // q) # x^2 + x + 1Compiled evaluation
from alkahest import compile_expr, eval_expr
# Scalar evaluation via a dict binding
result = eval_expr(x**2 + y, {x: 3.0, y: 1.0})
print(result) # 10.0
# JIT-compiled callable
f = compile_expr(x**2 + pool.integer(1), [x])
print(f([3.0])) # 10.0Vectorized evaluation over NumPy arrays
import numpy as np
from alkahest import compile_expr, numpy_eval
f = compile_expr(sin(x) * exp(pool.integer(-1) * x), [x])
xs = np.linspace(0, 10, 1_000_000)
ys = numpy_eval(f, xs) # vectorised; much faster than a Python loopContext manager
with alkahest.context(pool=pool, simplify=True):
z = alkahest.symbol("z") # uses the active pool
expr = z**2 + alkahest.sin(z)Agent / autoresearch loops
For a fan-out of candidates under a wall-clock or step budget, with results that survive context compaction:
import alkahest as ak
pool = ak.ExprPool()
x = pool.symbol("x")
with ak.context(pool=pool, budget=ak.Budget(wall_ms=100, seed=1)):
outs = ak.integrate_many([x**2, ak.sin(x)], x)
for item in outs:
if item.ok:
print(item.value.to_dict(mode="compact")["verification"]["status"])
else:
print(item.error["code"]) # e.g. E-INT-001 or E-BUDGET-001Full picture: Autoresearch / agent loops, Budgets, Batch, Claim graphs.
Run the Math playground locally
The playground's Math tab reads plain-language maths (integrate sin(x)^2 dx, solve x^2 - 5x + 6 = 0) and computes it with Alkahest. The site itself is static, so the calculations run on your machine:
pip install -U alkahest
python -m alkahest.playground # serves http://127.0.0.1:8765 to this computer onlyThen open the playground; it finds the server by itself (your browser may ask whether the site can connect to apps on your device: choose Allow). Nothing you type is sent anywhere else. It works in Chrome, Edge and Firefox; Safari blocks HTTPS pages from reaching http://127.0.0.1, so there run the playground itself locally (demo-playground/README.md). The server only answers the playground's origin, never runs Python from a request, and runs each calculation in a resource-limited child process; --port, --allow-origin and --quiet are the options. A WebAssembly build that needs no install is future work.
Running the examples
The examples/ directory in the Git repository has runnable end-to-end scripts. With alkahest installed (pip install alkahest or maturin develop as above), from the repository root run:
python examples/calculus.py
python examples/polynomials.py
python examples/jit_eval.py
python examples/ball_arithmetic.py
python examples/ode_modeling.pyIf you are developing without installing the extension into the active environment, set PYTHONPATH=python so the pure-Python package is importable alongside your build.