alkahest
Python API

Tensor calculus API

Metric → Christoffel → Riemann / Ricci / Einstein / Weyl on a coordinate chart, and an abstract-index layer with Einstein summation (tensor).

Every component is computed in an exact field: integer polynomials over the coordinates, the parameters and one generator per transcendental atom (sin/cos, sinh/cosh, exp, log, radicals, undefined functions such as a(t) and their Derivative nodes), with sin² = 1 − cos², cosh² − sinh² = 1, i² = −1 and (√b)² = b applied and every denominator made free of sin, radicals and i. That normal form is canonical, so a component that vanishes identically is the literal 0 and a non-zero one is never reported as zero — no sampling, no floats. Only independent components are computed; the rest follow by symmetry.

Conventions (MTW / Carroll / Wald): Γ^a_{bc} = ½ g^{ad}(∂_b g_{dc} + ∂_c g_{db} − ∂_d g_{bc}), R^a_{bcd} = ∂_c Γ^a_{db} − ∂_d Γ^a_{cb} + Γ^a_{ce}Γ^e_{db} − Γ^a_{de}Γ^e_{cb}, R_{bd} = R^a_{bad}, G_{ab} = R_{ab} − ½ R g_{ab}, K = R_{abcd}R^{abcd}; the 2-sphere of radius ρ has R = 2/ρ².

Metric

class
class Metric(g: Any, coords: Sequence[Expr])

A (pseudo-)Riemannian metric g_{ab} on the chart coords.

Parameters
glist of lists or Matrix

The symmetric components g[a][b] (Expr, int or Fraction).

coordssequence of Expr

Distinct coordinate symbols, one per row.

Raises
TensorError

E-TENSOR-001 (shape / coordinates), E-TENSOR-002 (not symmetric), E-TENSOR-003 (determinant identically zero), E-TENSOR-004 (a float entry), E-TENSOR-005 (Piecewise or another node the exact field cannot hold).

Every curvature method is computed once and cached; timings() reports the wall time of each stage.

Methods
as_tensor() → Tensor

g_{ab} as a Tensor (variance "dd").

inverse() → Tensor

g^{ab} (variance "uu").

determinant() → Expr

det g.

christoffel() → list

Christoffel symbols of the second kind: out[a][b][c] = Γ^a_{bc}.

christoffel_first_kind() → list

Christoffel symbols of the first kind: out[a][b][c] = Γ_{abc} = g_{ad}Γ^d_{bc}.

riemann() → Tensor

R^a_{bcd} (variance "uddd").

riemann_lower() → Tensor

R_{abcd} (variance "dddd").

ricci() → Tensor

R_{ab} = R^c_{acb} (variance "dd").

ricci_scalar() → Expr

R = g^{ab} R_{ab}.

einstein() → Tensor

G_{ab} = R_{ab} − ½ R g_{ab} (variance "dd").

weyl() → Tensor

The Weyl tensor C_{abcd} (variance "dddd"); E-TENSOR-008 for n < 3.

kretschmann() → Expr

K = R_{abcd} R^{abcd}.

is_ricci_flat() → bool

True iff every R_{ab} is exactly zero — a proof, not a sample.

geodesic_equations(velocities: Sequence[Any] | None=None) → list

Coordinate accelerations d²x^a/dτ² = −Γ^a_{bc} ẋ^b ẋ^c.

velocities defaults to fresh symbols <coord>_dot; any expressions may be passed (e.g. ak.diff(x(τ), τ)). Returns one Expr per coordinate.

tensor(components: Any, variance: str | Sequence[Any]) → Tensor

A Tensor bound to this metric (for raising/lowering).

simplify(expr: Any) → Any

Canonical form of expr in the exact component field (0 iff zero).

is_zero(expr: Any) → bool

Exact zero test in the component field.

diff(expr: Any, coord: Any) → Any

∂expr/∂coord in canonical form (coord an index or a coordinate).

timings() → dict[str, float]

Wall time in seconds of every stage computed so far.

Abstract indices

function
tensor_indices(names: str | Iterable[str]) → tuple[TensorIndex, ...]

Make indices from "a b c" (spaces or commas) or a list of names.

Returns a tuple of contravariant TensorIndex; negate one for the covariant position.

class
class Tensor(
    components: Any,
    variance: str | Sequence[Any],
    metric: Metric | None=None
)

Components of a tensor with a fixed variance per slot.

Parameters
componentsnested lists

components[i][j]... (rank-many levels; a bare Expr for rank 0).

variancestr

One character per slot: "u" (contravariant) or "d" (covariant), e.g. "uddd" for R^a_{bcd}.

metricMetric, optional

Used to raise/lower slots when indexed in the other variance, and to simplify components.

T[0, 1] (integers) is a component; T[-a, -b] (indices) is an IndexedTensor.

Properties
components: Any

Nested lists of components (zeros as 0).

Methods
component(*idx: int) → Any

The component at integer indices idx.

nonzero() → dict[tuple[int, ...], Any]

{index tuple: component} for every non-zero component.

class
class IndexedTensor(
    data: dict[tuple[int, ...], Any],
    free: Sequence[TensorIndex],
    dim: int,
    metric: Metric | None
)

A tensor with named free indices, the result of indexing a Tensor.

Supports +, -, * (outer product with Einstein summation over a repeated index, once up once down), scalar *//, symmetrize(), antisymmetrize(), and tensor() / components to read the result. Components are kept in the metric's canonical form, so a combination that vanishes is 0.

Properties
components: Any

Nested lists of components, slots in free order.

Methods
symmetrize(*idx: Any) → IndexedTensor

T_{(ab)} = ½(T_{ab} + T_{ba}) over the named free indices.

antisymmetrize(*idx: Any) → IndexedTensor

T_{[ab]} = ½(T_{ab} − T_{ba}) over the named free indices.

tensor() → Tensor

A Tensor with slots in free order.

component(*idx: int) → Any

The component at integer indices (slots in free order).

is_zero() → bool

True when every component is exactly zero (canonical form).

Refusals are TensorError (E-TENSOR-001 … E-TENSOR-010); see errors.

Indices

class
class TensorIndex(name: str, up: bool=True)

An abstract index. +a (or a) is contravariant, -a covariant.