Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

Galaga — Geometric Algebra for Python

Galaga 2 is a numeric geometric-algebra library built on a native Gram-matrix core. Its public API composes eager multivector values with optional immutable expression provenance, configurable blade conventions, and shared ASCII, Unicode, and LaTeX rendering.

  • General symmetric Gram matrices, not only diagonal signatures.
  • Long, unambiguous operation names as the primary mathematical API.
  • Optional concise aliases such as gp, op, rev, and sw.
  • Explicit inner products and contractions rather than one overloaded ip.
  • Immutable names and expression provenance over eager numeric values.
  • Presets that configure the metric, blade convention, notation, and model metadata together while still permitting component-level overrides.
  • Thread- and async-safe scoped presentation changes.
  • Read-only coefficient access and checked scalar conversion.

Install

For the Galaga 2 prerelease train:

python -m pip install --pre "galaga>=2.0.0a1,<3"

After stable 2.0.0 is published:

python -m pip install "galaga>=2,<3"

Galaga requires Python 3.11 or newer and NumPy. The optional galaga-anywidget visualization package also supports Python 3.11, while galaga-marimo requires Python 3.14 because it uses t-strings.

Quick start

from galaga import Algebra, DisplayPolicy, norm, outer_product

alg = Algebra((1, 1, 1), display=DisplayPolicy("full"))
e1, e2, e3 = alg.basis_vectors(expr=True)

u = (2 * e1 + e2).named("u")
v = (0.5 * e1 + 1.8 * e2).named("v")
area = outer_product(u, v)

print(area)
print(area.latex())
print(norm(area))

The operators are conveniences over named operations:

u * v  # geometric_product(u, v)
u ^ v  # outer_product(u, v)
u | v  # doran_lasenby_inner(u, v)
~u     # reverse(u)

Constructing an algebra

Diagonal algebras retain the familiar call shapes:

from galaga import Algebra

cl3 = Algebra(3)                       # Cl(3, 0)
sta = Algebra(1, 3)                    # Cl(1, 3)
pga = Algebra(3, 0, 1)                 # Cl(3, 0, 1)
ordered = Algebra((1, -1, -1, -1))    # explicit basis order

A full symmetric Gram matrix defines a non-orthogonal or native-null basis:

import numpy as np
from galaga import Algebra

null_plane = Algebra(
    gram=np.array(
        [
            [0.0, -1.0],
            [-1.0, 0.0],
        ]
    )
)

Complete presets configure numeric and presentation choices together:

from galaga import Algebra, p_cga, p_lengyel_cga, p_pga, p_rga, p_sta

sta = Algebra(config=p_sta("mostly-minus"))
pga = Algebra(config=p_pga(spatial_dim=3))
cga = Algebra(config=p_cga(spatial_dim=3, frame="null"))
lengyel_cga = Algebra(config=p_lengyel_cga())
rga = Algebra(config=p_rga(spatial_dim=3))

config= owns the whole algebra definition. The lower-level presentation=, blades=, notation=, local_names=, display_order=, and display= parameters permit deliberate overrides when constructing an algebra directly.

Native-null conformal model

The conformal preset stores eo and einf as actual null basis vectors in a non-diagonal Gram matrix. Attach the model-specific semantics explicitly:

from galaga import Algebra, outer_product, p_cga
from galaga.cga import ConformalModel

algebra = Algebra(config=p_cga(spatial_dim=3))
cga = ConformalModel(algebra, expr=True)
expanded_cga = cga.with_expression_form("expanded")

a = cga.up((0, 0, 0))
b = cga.up((1, 0, 0))
line = outer_product(a, b, cga.infinity)

assert cga.carrier(cga.round_point((1, 2, 3))).homogeneous_grade() == 2

round_point = cga.round_point((3, 4, 0), radius_squared=4)
assert float(cga.center_norm(round_point)) == 5
assert float(cga.radius_norm(round_point)) == 2

# The same value can explain itself with compact CGA vocabulary or its formula.
compact = cga.carrier(round_point)
expanded = expanded_cga.carrier(round_point)
assert compact == expanded

The native-null CGA guide covers round and flat objects, operator/expanded expression forms, att/car/ccr/cen/con/par, dual conventions, projection, Eric Lengyel's ●/○/■/□ components and weighted norms, and transformation recipes.

Point-based rigid model

p_rga() selects Eric Lengyel's point-based interpretation of Cl(3, 0, 1). Attach RigidModel when coordinates, projective measurement, projection, support, or validity constraints are needed:

from galaga import Algebra, p_rga
from galaga.rga import RigidModel

algebra = Algebra(config=p_rga())
rga = RigidModel(algebra, expr=True)

p = rga.point((3, 4, 0)).named("P")
q = rga.point((1, 0, 0)).named("Q")
line = p ^ q

assert float(rga.bulk_norm(p)) == 5
assert rga.is_valid_line(line)

The RGA guide covers the algebraic convention layer, paired norms, homogeneous distance and angle, projections, support, line/motor/flector constraints, transwedge correction, and the dual relationship with plane-based PGA.

Values, names, and expressions

Multivectors are eager and immutable. Naming and expression tracking return a new wrapper without changing the coefficients or the original value:

from galaga import Algebra, exp

alg = Algebra(3)
e1, e2, _ = alg.basis_vectors(expr=True)

theta = alg.scalar(0.6).named(r"\theta")
B = (e1 ^ e2).named("B")
R = exp(-theta * B / 2).named("R")

R.name       # immutable semantic Name
R.expr       # immutable expression provenance
R.numeric    # presentation-independent galaga.core.Multivector
R.data       # read-only coefficient array

Useful state transformations include:

R.named("Q")       # replace the semantic name
R.without_name()   # retain expression provenance
R.with_expr()      # attach literal provenance if absent
R.without_expr()   # retain the eager value and name

Factories accept expr=True when the construction itself should participate in a later expression tree:

e1, e2, e3 = alg.basis_vectors(expr=True)
scalar = alg.scalar(2, expr=True)
vector = alg.vector([1, 2, 3], expr=True)

Blades

blade() accepts configured labels, bitmasks, signed blade references, or an existing signed unit blade. blades() is the ordered plural form:

rga = Algebra(config=p_rga())
e1, e2, e3, e4 = rga.basis_vectors(expr=True)
e23, e31, e41, e42 = rga.blades(
    e2 ^ e3,
    e3 ^ e1,
    e4 ^ e1,
    e4 ^ e2,
    expr=True,
)

alg.locals() returns a read-only mapping for environments where bulk name injection is appropriate. In reactive notebooks, explicit blade() or blades() calls preserve dependency tracking more clearly.

Generated conventions support compact, juxtaposed, and wedge blade products:

Style LaTeX example
"compact" $e_{12}$
"juxtapose" $e_1 e_2$
"wedge" $e_1\wedge e_2$

Model-aware convention builders retain semantic roles while changing that spelling. Their dimension argument is the model's spatial dimension, not the total algebra dimension:

from galaga import Algebra, null_cga_blade_convention, p_cga

cga = Algebra(
    config=p_cga(spatial_dim=3),
    blades=null_cga_blade_convention(3, style="juxtapose"),
)

This is a five-dimensional algebra whose pseudoscalar renders as $e_1 e_2 e_3 e_o e_\infty$. The convention adds the origin and infinity vectors to the three Euclidean vectors itself.

Logarithms and geometric generators

log(A) is the real principal algebra logarithm, with no rotor requirement. It accepts positive scalar multivectors, vector exponentials, compound rotors and general mixed-grade inputs on its supported principal branch. Singular inputs, the spectral branch cut, and unresolved numerical cases raise rather than silently returning another branch or complex values.

Use rotor_generator(R) when the result must generate a path of rotors, or is_rotor_generator(B) to check a candidate independently. The generator operation validates the principal logarithm; it does not search alternative branches. A valid algebra logarithm need not be a geometric generator, even when its input is a rotor. The returned generator includes its full scale and any half-angle, rather than just a normalized plane.

The logarithms and generators notebook teaches these distinctions with computed Gram matrices, branch-cut examples, and an interactive comparison of algebraic and geometric paths to one rotor. From a checkout, run make run-marimo and open algebra/logarithms_and_generators.py in the gallery (Python 3.14).

Product and contraction family

The long names are canonical:

Family Canonical operation
Clifford product geometric_product
Exterior product outer_product
Left and right contractions left_contraction, right_contraction
Doran–Lasenby inner doran_lasenby_inner
Hestenes inner hestenes_inner
Metric inner metric_inner_product
Scalar product scalar_product
Lengyel antidot antidot_product
Regressive product regressive_product
Transwedge family transwedge, transwedge_antiproduct

There is deliberately no top-level ip or inner_product: these operations disagree for mixed grades and scalar inputs. Choose the intended definition explicitly, or define a local notation:

from galaga import doran_lasenby_inner as ip

Variadic geometric and outer products lower left-to-right through their binary catalog operation:

from galaga import geometric_product, outer_product

geometric_product(a, b, c)
outer_product(e1, e2, e3)

Commutators and anticommutators are unscaled:

commutator(a, b)            # ab - ba
anticommutator(a, b)        # ab + ba
half_commutator(a, b)       # (ab - ba) / 2
half_anticommutator(a, b)   # (ab + ba) / 2
lie_bracket(a, b)           # unscaled commutator
jordan_product(a, b)        # unscaled anticommutator

Grades and scalar conversion

from galaga import grade, grades, scalar_part

scalar_component = grade(value, 0)      # scalar multivector
selected = grades(value, [0, 2, 4])
coefficient = float(grade(value, 0))
same_coefficient = scalar_part(value)   # optional helper

float(value) succeeds only when the entire multivector is scalar. It never silently discards non-scalar grades. The explicit .data property exposes the read-only NumPy coefficient array:

coefficients = value.data

Multivectors deliberately do not implement NumPy's array or ufunc protocols; np.asarray(value) is not a coefficient conversion.

Rendering and presentation

DisplayPolicy chooses content independently from the target format:

from galaga import Algebra, DisplayPolicy

alg = Algebra(3, display=DisplayPolicy(content="full"))
e1, e2, _ = alg.basis_vectors(expr=True)
x = (2 * e1 + e2).named("x")

x.display("full/latex")
x.display("expr/unicode")
x.display("value/ascii")
x.latex()

Persistent presentation changes return cheap algebra views sharing the same numeric algebra. Scoped overrides use ContextVar, so they are isolated by thread and asynchronous task:

teaching_alg = alg.with_presentation(teaching_presentation)

with alg.use_presentation(teaching_presentation):
    print(x.latex())

Public API and numeric core

Import application APIs from galaga. Internally, those public objects are owned by the composition facade:

import galaga
import galaga.facade

assert galaga.Algebra is galaga.facade.Algebra
assert galaga.Multivector is galaga.facade.Multivector

Use galaga.core when presentation, names, and expression provenance are not needed:

from galaga.core import Algebra, geometric_product

numeric = Algebra(gram=[[1.0, 0.2], [0.2, 1.0]])
e1, e2 = numeric.basis_vectors()
result = geometric_product(e1, e2)

Legacy engine removed

The Galaga 1 table engine and its temporary galaga.legacy namespace no longer ship. Use the public galaga API or galaga.core for numeric-only work. Old expr, symbolic_core, notation and latex_* implementation paths are removed too; their replacements are galaga.expression, galaga.presentation, galaga.rendering and galaga.names.

Historical regression evidence remains in the repository, not in a second installed engine. See the migration guide for explicit naming, provenance and rendering replacements.

More documentation

Executable examples live in the repository gallery.

Metadata

Release files for galaga 2.0.0a3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for galaga 2.0.0a3
File Size Uploaded Provenance Actions
galaga-2.0.0a3.tar.gz 580.2 kB Without provenance Details for galaga-2.0.0a3.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for galaga 2.0.0a3
File Interpreter ABI Platform Actions
galaga-2.0.0a3-py3-none-any.whl Python 3 none any Details for galaga-2.0.0a3-py3-none-any.whl

Total release size: 687.4 kB

Release files / galaga-2.0.0a3.tar.gz

Download URL galaga-2.0.0a3.tar.gz
Size 580.2 kB
Tags Source
SHA-256 checksum
How to use checksums
9409cd3832471a4d93844e01d4cc2e96762411f0596e54791d4af0bf6988a9c5
BLAKE2b-256 checksum
How to use checksums
b424ad8ff37f23d3c94a5948888c42f4d6523123aadf5f03b715d2d789148f19
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / galaga-2.0.0a3-py3-none-any.whl

Download URL galaga-2.0.0a3-py3-none-any.whl
Size 107.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c0230057e74910d7bbe053b4c808da561fb343116c12992af9194f45becaaf75
BLAKE2b-256 checksum
How to use checksums
f9ca4f2edb6eea2f36a1ba562eda39ae5df005b7274f276af601bf4ba7ed9199
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for galaga 2.0.0a3

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.

Release history Release notifications | RSS feed

This release
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page