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,
The geometric algebra consists of scalars, two vectors, and a bivector ,
A complex number can be directly associated with a 2D spinor in the \(e_{12}\) -plane,
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}\) .
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)