The Algebra of Space (G3)
This notebook is an adaptation from the clifford python documentation.
Import Grassmann
and instantiate a three dimensional geometric algebra
julia> using Grassmann
julia> basis"3"
(⟨×××⟩, v, v₁, v₂, v₃, v₁₂, v₁₃, v₂₃, v₁₂₃)
Given a three dimensional GA with the orthonormal basis $v_i\cdot v_j = \delta_{ij}$, the basis consists of scalars, three vectors, three bivectors, and a trivector.
\[\{\underbrace{v}_{\text{scalar}},\qquad\underbrace{v_1,v_2,v_3}_{\text{vectors}},\qquad\underbrace{v_{12},v_{23},v_{13}}_{\text{bivectors}},\qquad\underbrace{v_{123}}_{\text{trivector}}\}\]
The @basis
macro declares the algebra and assigns the SubManifold
elements to local variables. The Basis
can also be assigned to G3
as
julia> G3 = Λ(3)
DirectSum.Basis{⟨×××⟩,8}(v, v₁, v₂, v₃, v₁₂, v₁₃, v₂₃, v₁₂₃)
You may wish to explicitly assign the blades to variables like so,
e1 = G3.v1
e2 = G3.v2
# etc ...
Or, if you're lazy you can use the macro with different local names
julia> @basis ℝ^3 E e
(⟨+++⟩, v, v₁, v₂, v₃, v₁₂, v₁₃, v₂₃, v₁₂₃)
julia> e3, e123
(v₃, v₁₂₃)
Basics
The basic products are available
julia> v1 * v2 # geometric product
v₁₂
julia> v1 | v2 # inner product
0v
julia> v1 ∧ v2 # exterior product
v₁₂
julia> v1 ∧ v2 ∧ v3 # even more exterior products
v₁₂₃
Multivectors can be defined in terms of the basis blades. For example, you can construct a rotor as a sum of a scalar and a bivector, like so
julia> θ = π/4
0.7853981633974483
julia> R = cos(θ) + sin(θ)*v23
0.7071067811865476 + 0.7071067811865475v₂₃
julia> R = exp(θ*v23)
0.7071067811865476 + 0.7071067811865475v₂₃
You can also mix grades without any reason
julia> A = 1 + 2v1 + 3v12 + 4v123
1 + 2v₁ + 3v₁₂ + 4v₁₂₃
The reversion operator is accomplished with the tilde ~
in front of the MultiVector
on which it acts
julia> ~A
1 + 2v₁ - 3v₁₂ - 4v₁₂₃
Taking a projection into a specific grade
of a MultiVector
is usually written $\langle A\rangle_n$ and can be done using the soft brackets, like so
julia> A(0)
1v
julia> A(1)
2v₁ + 0v₂ + 0v₃
julia> A(2)
3v₁₂ + 0v₁₃ + 0v₂₃
Using the reversion and grade projection operators, we can define the magnitude of A
as $|A|^2 = \langle\tilde A A\rangle$
julia> ~A*A
30 + 4v₁ + 12v₂ + 24v₃
julia> scalar(ans)
30v
This is done in the abs
and abs2
operators
julia> abs2(A)
30 + 2v₁ + 6v₂ + 12v₃ + 3v₁₂ + 8v₂₃ + 4v₁₂₃
julia> scalar(ans)
30v
The dual of a multivector A
can be defined as $\tilde AI$, where I
is the pseudoscalar for the geometric algebra. In G3
, the dual of a vector is a bivector:
julia> a = 1v1 + 2v2 + 3v3
1v₁ + 2v₂ + 3v₃
julia> ⋆a
3v₁₂ - 2v₁₃ + 1v₂₃
Reflections
Reflecting a vector $c$ about a normalized vector $n$ is pretty simple, $c\mapsto -ncn$
julia> c = v1+v2+v3 # a vector
1v₁ + 1v₂ + 1v₃
julia> n = v1 # the reflector
v₁
julia> -n*c*n # reflect a in hyperplane normal to n
0 - 1v₁ + 1v₂ + 1v₃
Because we have the inv
available, we can equally well reflect in un-normalized vectors using $a\mapsto n^{-1}an$
julia> a = v1+v2+v3 # the vector
1v₁ + 1v₂ + 1v₃
julia> n = 3v1 # the reflector
3v₁
julia> inv(n)*a*n
0.0 + 1.0v₁ - 1.0v₂ - 1.0v₃
julia> n\a*n
0.0 + 1.0v₁ - 1.0v₂ - 1.0v₃
Reflections can also be made with respect to the hyperplane normal to the vector, in which case the formula is negated.
Rotations
A vector can be rotated using the formula $a\mapsto \tilde R aR$, where R
is a rotor. A rotor can be defined by multiple reflections, $R = mn$ or by a plane and an angle $R = e^{\theta B/2}$. For example,
julia> R = exp(π/4*v12)
0.7071067811865476 + 0.7071067811865475v₁₂
julia> ~R*v1*R
0.0 + 2.220446049250313e-16v₁ + 1.0v₂
Maybe we want to define a function which can return rotor of some angle $\theta$ in the $v_{12}$-plane, $R_{12} = e^{\theta v_{12}/2}$
R12(θ) = exp(θ/2*v12)
And use it like this
julia> R = R12(π/2)
0.7071067811865476 + 0.7071067811865475v₁₂
julia> a = v1+v2+v3
1v₁ + 1v₂ + 1v₃
julia> ~R*a*R
0.0 - 0.9999999999999997v₁ + 1.0v₂ + 1.0v₃
You might as well make the angle argument a bivector, so that you can control the plane of rotation as well as the angle
R_B(B) = exp(B/2)
Then you could do
julia> Rxy = R_B(π/4*v12)
0.9238795325112867 + 0.3826834323650898v₁₂
julia> Ryz = R_B(π/5*v23)
0.9510565162951535 + 0.3090169943749474v₂₃
or
julia> R_B(π/6*(v23+v12))
0.9322404424570728 + 0.25585909935689327v₁₂ + 0.25585909935689327v₂₃
Maybe you want to define a function which returns a function that enacts a specified rotation, $f(B) = a\mapsto e^{B/2}\\ae^{B/2}$. This just saves you having to write out the sandwich product, which is nice if you are cascading a bunch of rotors, like so
R_factory(B) = (R = exp(B/2); a -> ~R*a*R)
Rxy = R_factory(π/3*v12)
Ryz = R_factory(π/3*v23)
Rxz = R_factory(π/3*v13)
Then you can do things like
julia> R = R_factory(π/6*(v23+v12)) # this returns a function
#1 (generic function with 1 method)
julia> R(a) # which acts on a vector
0.0 + 0.5229556000177233v₁ + 0.7381444851051178v₂ + 1.4770443999822769v₃
julia> Rxy(Ryz(Rxz(a)))
0.0 + 0.40849364905389035v₁ - 0.6584936490538903v₂ + 1.5490381056766584v₃
To make cascading a sequence of rotations as concise as possible, we could define a function which takes a list of bivectors $A,B,C,...$, and enacts the sequence of rotations which they represent on some vector $x$.
julia> R_seq(args...) = (R = prod(exp.(args./2)); a -> ~R*a*R)
R_seq (generic function with 1 method)
julia> R = R_seq(π/2*v23, π/2*v12, v1)
#3 (generic function with 1 method)
julia> R(v1)
2.220446049250313e-16 + 3.3306690738754696e-16v₁ + 0.9999999999999998v₂ + 5.551115123125783e-17v₂₃
Barycentric Coordinates
We can find the barycentric coordinates of a point in a triangle using area ratios.
julia> function barycoords(p, a, b, c)
ab = b-a
ca = a-c
bc = c-b
A = -ab∧ca
(bc∧(p-b)/A, ca∧(p-c)/A, ab∧(p-a)/A)
end
barycoords (generic function with 1 method)
julia> barycoords(0.25v1+0.25v2, 0v1, 1v1, 1v2)
(0.5v⃖, 0.25v⃖, 0.25v⃖)