This page was generated from docs/tutorials/g3-algebra-of-space.ipynb. Interactive online version: Binder badge.

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.


First, we import clifford as cf, and instantiate a three dimensional geometric algebra using Cl() (docs).

import clifford as cf

layout, blades = cf.Cl(3)  # creates a 3-dimensional clifford algebra

Given a three dimensional GA with the orthonormal basis,

\[e_{i}\cdot e_{j}=\delta_{ij}\]

The basis consists of scalars, three vectors, three bivectors, and a trivector.

\[\{\hspace{0.5em} \underbrace{\hspace{0.5em}\alpha,\hspace{0.5em}}_{\mbox{scalar}}\hspace{0.5em} \underbrace{\hspace{0.5em}e_{1},\hspace{1.5em}e_{2},\hspace{1.5em}e_{3},\hspace{0.5em}}_{\mbox{vectors}}\hspace{0.5em} \underbrace{\hspace{0.5em}e_{12},\hspace{1.5em}e_{23},\hspace{1.5em}e_{13},\hspace{0.5em}}_{\mbox{bivectors}}\hspace{0.5em} \underbrace{\hspace{0.5em}e_{123}\hspace{0.5em}}_{\text{trivector}} \hspace{0.5em} \}\]

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,

{'': 1,
 'e1': (1^e1),
 'e2': (1^e2),
 'e3': (1^e3),
 'e12': (1^e12),
 'e13': (1^e13),
 'e23': (1^e23),
 'e123': (1^e123)}

You may wish to explicitly assign the blades to variables like so,

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.


Now, all the blades have been defined in the local namespace

e3, e123
((1^e3), (1^e123))



The basic products are available

e1*e2  # geometric product
e1|e2  # inner product
e1^e2  # outer product
e1^e2^e3  # even more outer products

Defects in Precedence

Python’s operator precedence makes the outer product evaluate after addition. This requires the use of parentheses when using outer products. For example

e1^e2 + e2^e3  # fail, evaluates as
(e1^e2) + (e2^e3)  # correct
(1^e12) + (1^e23)

Also the inner product of a scalar and a Multivector is 0,


So for scalars, use the outer product or geometric product instead



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

from scipy import cos, sin

theta = pi/4
R = cos(theta) - sin(theta)*e23
NameError                                 Traceback (most recent call last)
<ipython-input-14-7dddb582d1e8> in <module>
      1 from scipy import cos, sin
----> 3 theta = pi/4
      4 R = cos(theta) - sin(theta)*e23
      5 R

NameError: name 'pi' is not defined

You can also mix grades without any reason

A = 1 + 2*e1 + 3*e12 + 4*e123
1 + (2^e1) + (3^e12) + (4^e123)


The reversion operator is accomplished with the tilde ~ in front of the Multivector on which it acts

1 + (2^e1) - (3^e12) - (4^e123)

Grade Projection

Taking a projection onto a specific grade \(n\) of a Multivector is usually written

\[\langle A \rangle _n\]

can be done by using soft brackets, like so

A(0)  # get grade-0 elements of R
A(1)  # get grade-1 elements of R
A(2)  # you get it


Using the reversion and grade projection operators, we can define the magnitude of \(A\)

\[|A|^2 = \langle A\tilde{A}\rangle\]

This is done in the abs() operator



The inverse of a Multivector is defined as \(A^{-1}A=1\)

0.13415 + (0.12195^e1) - (0.14634^e3) + (0.18293^e12) + (0.09756^e23) - (0.29268^e123)


The dual of a multivector \(A\) can be defined as


Where, \(I\) is the pseudoscalar for the GA. In \(G_3\), the dual of a vector is a bivector,

a = 1*e1 + 2*e2 + 3*e3
-(3.0^e12) + (2.0^e13) - (1.0^e23)

Pretty, Ugly, and Display Precision

You can toggle pretty printing with with pretty() or ugly(). ugly returns an eval-able string.

MultiVector(Layout([1, 1, 1],
                   names=['', 'e1', 'e2', 'e3', 'e12', 'e13', 'e23', 'e123']),
            [0.13414634146341464, 0.12195121951219513, -0.0, -0.14634146341463417, 0.18292682926829273, -2.0816681711721682e-17, 0.0975609756097561, -0.29268292682926833])

You can also change the displayed precision


0.13 + (0.12^e1) - (0.15^e3) + (0.18^e12) + (0.1^e23) - (0.29^e123)

This does not effect the internal precision used for computations.



reflection on vector

Reflecting a vector \(c\) about a normalized vector \(n\) is pretty simple,

\[c \rightarrow ncn\]
c = e1+e2+e3    # a vector
n = e1          # the reflector
n*c*n          # reflect `a` in hyperplane normal to `n`
(1^e1) - (1^e2) - (1^e3)

Because we have the inv() available, we can equally well reflect in un-normalized vectors using,

\[a \rightarrow nan^{-1}\]
a = e1+e2+e3    # the vector
n = 3*e1          # the reflector
(1.0^e1) - (1.0^e2) - (1.0^e3)

Reflections can also be made with respect to the a ‘hyperplane normal to the vector \(n\)’, in this case the formula is negated

\[c \rightarrow -ncn^{-1}\]


A vector can be rotated using the formula

\[a \rightarrow Ra\tilde{R}\]

Where \(R\) is a rotor. A rotor can be defined by multiple reflections,


or by a plane and an angle,

\[R = e^{-\frac{\theta}{2}\hat{B}}\]

For example

import math

R = math.e**(-math.pi/4*e12) # enacts rotation by pi/2
0.71 - (0.71^e12)
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,

\[R_{12} = e^{-\frac{\theta}{2}e_{12}}\]
R12 = lambda theta: e**(-theta/2*e12)
NameError                                 Traceback (most recent call last)
<ipython-input-31-517400d2f6b9> in <module>
      1 R12 = lambda theta: e**(-theta/2*e12)
----> 2 R12(pi/2)

NameError: name 'pi' is not defined

And use it like this

a = e1+e2+e3
R = R12(math.pi/2)

NameError                                 Traceback (most recent call last)
<ipython-input-32-62fda315a2cc> in <module>
      1 a = e1+e2+e3
----> 2 R = R12(math.pi/2)
      3 R*a*~R

<ipython-input-31-517400d2f6b9> in <lambda>(theta)
----> 1 R12 = lambda theta: e**(-theta/2*e12)
      2 R12(pi/2)

NameError: name 'e' is not defined

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 = e^{-\frac{B}{2}}\]
R_B = lambda B: math.e**(-B/2)

Then you could do

R12 = R_B(math.pi/4*e12)
R23 = R_B(math.pi/5*e23)


R_B(math.pi/6*(e23+e12))  # rotor enacting a pi/6-rotation in the e23+e12-plane
0.93 - (0.26^e12) - (0.26^e23)

Maybe you want to define a function which returns a function that enacts a specified rotation,

\[f(B) \rightarrow \underline{R_B}(a) = R_Ba\tilde{R_B}\]

This just saves you having to write out the sandwich product, which is nice if you are cascading a bunch of rotors, like so

\[\underline{R_C}( \underline{R_B}( \underline{R_A}(a)))\]
def R_factory(B):
    def apply_rotation(a):
        R = math.e**(-B/2)
        return R*a*~R
    return apply_rotation

R = R_factory(pi/6*(e23+e12))  # this returns a function
R(a)  # which acts on a vector
NameError                                 Traceback (most recent call last)
<ipython-input-36-402a96f6039a> in <module>
      5     return apply_rotation
----> 7 R = R_factory(pi/6*(e23+e12))  # this returns a function
      8 R(a)  # which acts on a vector

NameError: name 'pi' is not defined

Then you can do things like

R12 = R_factory(math.pi/3*e12)
R23 = R_factory(math.pi/3*e23)
R13 = R_factory(math.pi/3*e13)

(0.41^e1) - (0.66^e2) + (1.55^e3)

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\).

\[f(A,B,C,x) = \underline{R_A} (\underline{R_B} (\underline{R_C}(x)))\]
from functools import reduce

# a sequence of rotations
def R_seq(*args):
    *Bs, x = args
    R_lst = [math.e**(-B/2) for B in Bs]  # create list of Rotors from list of Bivectors
    R = reduce(, R_lst)          # apply the geometric product to list of Rotors
    return R*x*~R

# rotation sequence by  pi/2-in-e12 THEN pi/2-in-e23
R_seq(pi/2*e23, pi/2*e12, e1)
NameError                                 Traceback (most recent call last)
<ipython-input-38-21fc9c28e3a1> in <module>
     10 # rotation sequence by  pi/2-in-e12 THEN pi/2-in-e23
---> 11 R_seq(pi/2*e23, pi/2*e12, e1)

NameError: name 'pi' is not defined

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,

layout,blades = cf.Cl(2, names=['','x','y','i'])

{'': 1, 'x': (1^x), 'y': (1^y), 'i': (1^i)}
1*x + 2*y
(1^x) + (2^y)
1 + 4*i
1 + (4^i)