Design and Internals

Multivector Types

There are two concrete types for representing elements in a geometric algebra:

         AbstractMultivector{Sig}
            /               \                             
BasisBlade{Sig,K,T}    Multivector{Sig,K,S}
  • BasisBlade: a scalar multiple of a wedge product of orthogonal basis vectors.
  • Multivector: a homogeneous or inhomogeneous multivector; a sum of basis blades.

Type parameters:

  • Sig: The metric signature which defines the geometric algebra. This can be any all-bits value which satisfies the metric signature interface.
  • K: The grade(s) of a multivector. For BasisBlades, this is an integer, but for Multivectors, it may be a collection (e.g., 0:3 for a general 3D multivector).
  • T: The numerical type of the coefficient of a BasisBlade.
  • S: The storage type of the components of a Multivector, usually an AbstractVector subtype.

Metric Signatures

The metric signature type parameter Sig defines the dimension of the geometric algebra and the norms of its standard orthonormal basis vectors. Additionally, it allows various default behaviours to be customised through method definitions which dispatch on Sig, as detailed in the metric signature interface.

By default, the following metric signature types are implemented:

  • Int, defining a Euclidean metric of that dimension,
  • Tuple, defining the norms of each basis vector,
  • NamedTuple, defining basis vector labels as well as norms,
  • Cl, a type resembling the notation $Cl(p, q, r)$ common in literature.
julia> @basis 2
[ Info: Defined basis blades v1, v2, v12, I in Main

julia> basis((t=-1, x=1, y=1, z=1)) |> prod
BasisBlade{(t = -1, x = 1, y = 1, z = 1), 4, Int64}:
 1 txyz

julia> sum(basis(Cl("++"))) # shorthand for metric signature (1, 1)
2-component Multivector{Cl("++"), 1, MVector{2, Int64}}:
 1 v1
 1 v2

The metric signature interface

The metric signature type parameter may be any isbits value satisying the following interface. As well as defining the geometric algebra, the signature is used to specify basis blade labels, the default array type for multivector components, and other metadata.

Required methodsDescription
dimension(sig)The dimension of the underlying vector space, or number of basis vectors.
basis_vector_square(sig, i)The scalar square of the ith basis vector.
Optional methodsDescription
show_signature(io, sig)Show the metric signature in a compact human-readable form.
show_basis_blade(io, sig, indices)Print a basis blade with the given indices (e.g., v12 or 𝒆₁∧𝒆₂).
bits_to_indices(sig, bits)Define display order of indices for a basis blade (must also implement basis_blade_parity(sig, bits) consistently).
componentstype(sig, N)Preferred array type for Multivector{sig} components. (E.g., Vector, MVector, SparseVector, etc.)
use_symbolic_optim(sig)Whether to use symbolic code generation to optimise multivector products. (Default is true for low dimensions.)

Below is an example of how one might define a “projectivised” signature which adds a projective dimension $𝐯_0$ squaring to $-1$ to any signature:

import GeometricAlgebra: dimension, basis_vector_square, show_signature, show_basis_blade

struct ℙ{Sig} end
ℙ(sig) = ℙ{sig}()

dimension(::ℙ{Sig}) where Sig = dimension(Sig) + 1
basis_vector_square(::ℙ{Sig}, i) where Sig = i == 1 ? -1 : basis_vector_square(Sig, i - 1)
show_signature(io::IO, ::ℙ{Sig}) where Sig = print(io, "ℙ($Sig)")

show_basis_blade(io::IO, ::ℙ, indices::Vector) = print(io, "v", join(indices .- 1))

basis(ℙ(3)) |> sum
4-component Multivector{ℙ(3), 1, MVector{4, Int64}}:
 1 v0
 1 v1
 1 v2
 1 v3

Symbolic Algebra and Code Generation

Thanks to the wonderful SymbolicUtils package, the same code originally written for numerical multivectors readily works with symbolic components. For example, we can compute the product of two vectors symbolically as follows:

julia> GeometricAlgebra.make_symbolic.(Multivector{2,1}, [:A, :B])
2-element Vector{Multivector{2, 1, Vector{Any}}}:
 A[1]v1 + A[2]v2
 B[1]v1 + B[2]v2

julia> prod(ans)
2-component Multivector{2, 0:2:2, MVector{2, SymbolicUtils.BasicSymbolic{Real}}}:
 A[1]*B[1] + A[2]*B[2]
 A[1]*B[2] - A[2]*B[1] v12

This makes it easy to optimize multivector operations: first perform the calculation symbolically and then compile the resulting analytic expression. By default, this optimization is enabled for most products (including the geometric, wedge and inner products in up to eight dimensions[1]). This is done by prefixing method definitions with the internal @symbolic_optim macro.

Symbolic optimisation is also exposed through a user-facing macro @symbolicga, inspired by the @ga macro in serenity4/SymbolicGA.jl. This is especially useful when you want use geometric algebra without manipulating Multivector types.

For example, using $Cl(ℝ³)$ to represent homogeneous coordinates on the plane:

julia> joinpoints(p, q) = @symbolicga 3 (p=1, q=1) p ∧ q Tuple
joinpoints (generic function with 1 method)

julia> intersectlines(p, q) = @symbolicga 3 (p=2, q=2) rdual(ldual(p) ∧ ldual(q)) Tuple
intersectlines (generic function with 1 method)

julia> L1 = joinpoints((1, 0, 1), (0, 1, 1)) # line y = 1 - x
(1, 1, -1)

julia> L2 = joinpoints((0, 0, 1), (1, 1, 1)) # line y = x
(0, -1, -1)

julia> intersectlines(L1, L2) # point (0.5, 0.5)
(-1, -1, -2)

The resulting methods are loop-free and allocation-free.