Interfacing Other Mathematical Systems

Geometric Algebra is known as a universal algebra because it subsumes several other mathematical systems. Two algebras commonly used by engineers and scientists are complex numbers and quaternions. These algebras can be subsumed as the even sub-algebras of 2 and 3 dimensional geometric algebras, respectively. This notebook demonstrates how clifford can be used to incorporate data created with these systems into geometric algebra.

Complex Numbers

Given a two dimensional GA with the orthonormal basis,

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

The geometric algebra consists of scalars, two vectors, and a bivector ,

\[\{\underbrace{\alpha}_{\mbox{scalar}},\qquad\underbrace{e_{1},e_{2}}_{\mbox{vector}},\qquad\underbrace{e_{12}}_{\mbox{bivector}}\}\]

A complex number can be directly associated with a 2D spinor in the \(e_{12}\) -plane,

\[\underbrace{\mathbf{z}=\alpha+\beta i}_{\mbox{complex number}}\quad\Longrightarrow\quad\underbrace{Z=\alpha+\beta e_{12}}_{\mbox{2D spinor}}\]

The even subalgebra of a two dimensional geometric algebra is isomorphic to the complex numbers. We can setup translating functions which converts a 2D spinor into a complex number and vice-versa. In two dimensions the spinor can be also be mapped into vectors if desired.

Below is an illustration of the three different planes, the later two being contained within the geometric algebra of two dimensions, \(G_2\). Both spinors and vectors in \(G_2\) can be modeled as points on a plane, but they have distinct algebraic properties.

In [1]:
from IPython.display import Image
Image(url='_static/2dmap.svg')
Out[1]:
In [2]:
from numpy import pi,e
import clifford as cf
layout, blades = cf.Cl(2) # instantiate a 2D- GA
locals().update(blades)   # put all blades into local namespace


def c2s(z):
    '''convert a complex number to a spinor'''
    return z.real + z.imag*e12

def s2c(S):
    '''convert a spinor to a complex number'''
    S0 = float(S(0))
    S2 = float(-S|e12)
    return S0 + S2*1j

Convert a complex number to a spinor

In [3]:
c2s(1+2j)
Out[3]:
1.0 + (2.0^e12)

Convert a spinor to a complex number

In [4]:
s2c(1+2*e12)
Out[4]:
(1+2j)

Make sure we get what we started with when we make a round trip

In [5]:
s2c(c2s(1+2j)) == 1+2j
Out[5]:
True

The spinor is then mapped to a vector by choosing a reference direction. This may be done by left multiplying with \(e_{1}\) .

\[Z\Longrightarrow e_{1}Z=e_{1}\alpha+\beta e_{1}e_{12}=\underbrace{\alpha e_{1}+\beta e_{2}}_{\mbox{vector}}\]
In [6]:
s = 1+2*e12
e1*s
Out[6]:
(1.0^e1) + (2.0^e2)

Geometrically, this is interpreted as having the spinor rotate a specific vector, in this case \(e_1\). Building off of the previously defined functions

In [7]:
def c2v(c):
    '''convert a complex number to a vector'''
    return e1*c2s(c)

def v2c(v):
    '''convert a vector to a complex number'''
    return s2c(e1*v)
In [8]:
c2v(1+2j)
Out[8]:
(1.0^e1) + (2.0^e2)
In [9]:
v2c(1*e1+2*e2)
Out[9]:
(1+2j)

Depending on your applications, you may wish to have the bivector be an argument to the c2s and s2c functions. This allows you to map input data given in the form of complex number onto the planes of your choice. For example, in three dimensional space there are three bivector-planes; \(e_{12}, e_{23}\) and \(e_{13}\), so there are many bivectors which could be interpreted as the unit imaginary.

Complex numbers mapped in this way can be used to enact rotations within the specified planes.

In [10]:

import clifford as cf
layout, blades = cf.Cl(3)
locals().update(blades)

def c2s(z,B):
    '''convert a complex number to a spinor'''
    return z.real + z.imag*B

def s2c(S,B):
    '''convert a spinor to a complex number'''
    S0 = float(S(0))
    S2 = float(-S|B)
    return S0 + S2*1j
In [11]:
c2s(1+2j,e23)
Out[11]:
1.0 + (2.0^e23)
In [12]:
c2s(3+4j,e13)
Out[12]:
3.0 + (4.0^e13)

This brings us to the subject of quaternions, which are used to handle rotations in three dimensions much like complex numbers do in two dimensions. With geometric algebra, they are just spinors acting in a different geometry.

Quaternions

Note:

There is support for quaternions in numpy through the package quaternion.

For some reason people think quaternions (wiki page) are mystical or something. They are just spinors in a three dimensional geometric algebra.

In either case, we can pass the names parameters to Cl() to explicitly label the bivectors i,j, and k.

In [13]:
import clifford as cf

# the vector/bivector order is reversed because Hamilton defined quaternions using a
# left handed frame. wtf.
names = ['','z','y','x','k','j','i','I']

layout, blades = cf.Cl(3, names=names)
locals().update(blades)

This leads to the commutations relations familiar to quaternion users

In [14]:
for m in [i,j,k]:
    for n in [i,j,k]:
        print ('%s*%s=%s'%(str(m),str(n),m*n))
(1^i)*(1^i)=-1.0
(1^i)*(1^j)=(1.0^k)
(1^i)*(1^k)=-(1.0^j)
(1^j)*(1^i)=-(1.0^k)
(1^j)*(1^j)=-1.0
(1^j)*(1^k)=(1.0^i)
(1^k)*(1^i)=(1.0^j)
(1^k)*(1^j)=-(1.0^i)
(1^k)*(1^k)=-1.0

Quaternion data could be stored in a variety of ways. Assuming you have the scalar components for the quaternion, all you will need to do is setup a map each component to the correct bivector.

In [15]:
def q2S(*args):
    '''
    convert tuple of quaternion coefficients to a spinor'''
    q = args
    return q[0] + q[1]*i +q[2]*j + q[3]*k

Then all the quaternion computations can be done using GA

In [16]:
q1 = q2S(1,2,3,4)
q1
Out[16]:
1.0 + (4.0^k) + (3.0^j) + (2.0^i)

This prints \(i,j\) and \(k\) in reverse order but whatever,

In [17]:
# 'scalar' part
q1(0)
Out[17]:
1.0
In [18]:
# 'vector' part (more like bivector part!)
q1(2)
Out[18]:
(4.0^k) + (3.0^j) + (2.0^i)

quaternion conjugation is implemented with reversion

In [19]:
~q1
Out[19]:
1.0 - (4.0^k) - (3.0^j) - (2.0^i)

The norm

In [20]:
abs(q1)
Out[20]:
5.477225575051661

Taking the dual() of the “vector” part actually returns a vector,

In [21]:
q1(2).dual()
Out[21]:
(2.0^z) - (3.0^y) + (4.0^x)
In [22]:
q1 = q2S(1,2,3,4)
q2 = q2S(5,6,7,8)

# quaternion product
q1*q2
Out[22]:
-60.0 + (24.0^k) + (30.0^j) + (12.0^i)

If you want to keep using a left-handed frame and names like \(i,j\) and \(k\) to label the planes in 3D space, ok. If you think it makes more sense to use the consistent and transparent approach provided by GA, we think you have good taste. If we make this switch, the basis and q2S() functions will be changed to

In [23]:
import clifford as cf
layout, blades = cf.Cl(3)
locals().update(blades)

blades
Out[23]:
{'e1': (1^e1),
 'e2': (1^e2),
 'e3': (1^e3),
 'e12': (1^e12),
 'e13': (1^e13),
 'e23': (1^e23),
 'e123': (1^e123)}
In [24]:


def q2S(*args):
    '''
    convert tuple of quaternion coefficients to a spinor'''
    q = args
    return q[0] + q[1]*e13 +q[2]*e23 + q[3]*e12

q1 = q2S(1,2,3,4)
q1
Out[24]:
1.0 + (4.0^e12) + (2.0^e13) + (3.0^e23)