The Algebra Of Space (G3)¶
In this notebook, we give a more detailed look at how to use
clifford
, using the algebra of three dimensional space as a context.
Setup¶
First, we import clifford as cf
, and instantiate a three dimensional
geometric algebra using Cl()
In [ ]:
from numpy import e,pi
import clifford as cf
layout, blades = cf.Cl(3) # creates a 3-dimensional clifford algebra
Given a three dimensional GA with the orthonormal basis,
The basis consists of scalars, three vectors, three bivectors, and a trivector.
Cl()
creates the algebra and returns a layout
and blades
.
The layout
holds information and functions related this instance of
G3
, and the blades
is a dictionary which contains the basis
blades, indexed by their string representations,
In [ ]:
blades
You may wish to explicitly assign the blades to variables like so,
In [ ]:
e1 = blades['e1']
e2 = blades['e2']
# etc ...
Or, if you’re lazy and just working in an interactive session you can
use locals()
to update your namespace with all of the blades at
once.
In [ ]:
locals().update(blades)
Now, all the blades have been defined in the local namespace
In [ ]:
e3, e123
Basics¶
Products¶
The basic products are available
In [ ]:
e1*e2 # geometric product
In [ ]:
e1|e2 # inner product
In [ ]:
e1^e2 # outer product
In [ ]:
e1^e2^e3 # even more outer products
Defects in Precidence¶
Python’s operator precidence makes the outer product evaluate after addition. This requires the use of parenthesis when using outer products. For example
In [ ]:
e1^e2+e2^e3 # fail
In [ ]:
(e1^e2) + (e2^e3) # correct
Also the inner product of a scalar and a Multivector is 0,
In [ ]:
4|e1
So for scalars, use the outer product or geometric product instead
In [ ]:
4*e1
Multivectors¶
Multivectors can be defined in terms of the basis blades. For example you can construct a rotor as a sum of a scalar and bivector, like so
In [ ]:
from scipy import cos, sin
theta = pi/4
R = cos(theta) - sin(theta)*e23
R
You can also mix grades without any reason
In [ ]:
A = 1 + 2*e1 + 3*e12 + 4*e123
A
Reversion¶
The reversion operator is accomplished with the tilde ~
in front of
the Multivector on which it acts
In [ ]:
~A
Grade Projection¶
Taking a projection onto a specific grade \(n\) of a Multivector is usually written
can be done by using soft brackets, like so
In [ ]:
A(0) # get grade-0 elements of R
In [ ]:
A(1) # get grade-1 elements of R
In [ ]:
A(2) # you get it
Magnitude¶
Using the reversion and grade projection operators, we can define the magnitude of \(A\)
In [ ]:
(A*~A)(0)
This is done in the abs()
operator
In [ ]:
abs(A)**2
Dual¶
The dual of a multivector \(A\) can be defined as
Where, \(I\) is the psuedoscalar for the GA. In \(G_3\), the dual of a vector is a bivector,
In [ ]:
a = 1*e1 + 2*e2 + 3*e3
a.dual()
Pretty, Ugly, and Display Precision¶
You can toggle pretty printing with with pretty()
or ugly()
.
ugly
returns an eval-able string.
In [ ]:
cf.ugly()
A.inv()
You can also change the displayed precision
In [ ]:
cf.pretty(precision=2)
A.inv()
This does not effect the internal precision used for computations.
Applications¶
Reflections¶
In [ ]:
from IPython.display import Image
Image(url='_static/reflection_on_vector.svg')
Reflecting a vector \(c\) about a normalized vector \(n\) is pretty simple,
In [ ]:
c = e1+e2+e3 # a vector
n = e1 # the reflector
n*c*n # reflect `a` in hyperplane normal to `n`
Because we have the inv()
available, we can equally well reflect in
un-normalized vectors using,
In [ ]:
a = e1+e2+e3 # the vector
n = 3*e1 # the reflector
n*a*n.inv()
Refelections can also be made with repsect to the a ‘hyperplane normal to the vector \(n\)’, in this case the formula is negated
Rotations¶
A vector can be rotated using the formula
Where \(R\) is a rotor. A rotor can be defined by multiple reflections,
or by a plane and an angle,
For example
In [ ]:
from numpy import pi
R = e**(-pi/4*e12) # enacts rotation by pi/2
R
In [ ]:
R*e1*~R # rotate e1 by pi/2 in the e12-plane
Some Ways to use Functions¶
Maybe we want to define a function which can return rotor of some angle \(\theta\) in the \(e_{12}\)-plane,
In [ ]:
R12 = lambda theta: e**(-theta/2*e12)
R12(pi/2)
And use it like this
In [ ]:
a = e1+e2+e3
R = R12(pi/2)
R*a*~R
You might as well make the angle arugment a bivector, so that you can control the plane of rotation as well as the angle
In [ ]:
R_B = lambda B: e**(-B/2.)
Then you could do
In [ ]:
R12 = R_B(pi/4*e12)
R23 = R_B(pi/5*e23)
or
In [ ]:
R_B(pi/6*(e23+e12)) # rotor enacting a pi/6-rotation in the e23+e12-plane
Maybe you want to define a function which returns a function that enacts a specified rotation,
This just saves you having to write out the sandwhich product, which is nice if you are cascading a bunch of rotors, like so
In [ ]:
def R_factory( B):
def dummy_f(a):
R = e**(-B/2)
return R*a*~R
return dummy_f
R = R_factory(pi/6*(e23+e12)) # this returns a function
R(a) # which acts on a vector
Then you can do things like
In [ ]:
R12 = R_factory(pi/3*e12)
R23 = R_factory(pi/3*e23)
R13 = R_factory(pi/3*e13)
R12(R23(R13(a)))
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 a some vector \(x\).
In [ ]:
from functools import reduce
# a sequence of rotations
def R_seq(*args):
Bs,a = args[:-1],args[-1]
R_lst = [e**(-B/2) for B in Bs] # create list of Rotors from list of Bivectors
R = reduce(cf.gp, R_lst) # apply the geometric product to list of Rotors
return lambda a: R*a*~R
# rotation sequence by pi/2-in-e12 THEN pi/2-in-e23
R = R_seq(pi/2*e23, pi/2*e12, e1)
R(e1)
Changing Basis Names¶
If you want to use different names for your basis as opposed to e’s with
numbers, supply the Cl()
with a list of names
. For example for a
two dimensional GA,
In [ ]:
layout,blades = cf.Cl(2, names = ['','x','y','i'])
blades
In [ ]:
locals().update(blades)
In [ ]:
1*x+2*y
In [ ]:
(1+4*i)