Conformal Geometric Algebra with Python

Part 1

Hugo Hadfield 2018

Why Python?

  • Swiftly becoming THE STANDARD for scientific computing
  • Clean and easy syntax
  • Huge number of third party libraries
  • Good interoperability with Julia, Fortran, C++ etc.
  • Free and open source
    • Tying your skill set to a specific company's product (eg. Matlab) is a bad idea!

Why Conformal GA?

  • Geometric primitives are elements of the algebra
  • All conformal transformations are elements of the algebra
  • Good for representing 3D space!

The Python GA ecosystem

 

ALL BUILT ON STANDARD LIBRARIES FOR SCIENTIFIC PYTHON

 

THE GOAL IS TO PROVIDE PROGRAMMERS THE TOOLS TO START WORKING IN GA WITHOUT HAVING TO READ A TEXTBOOK

The Clifford library - History

  • Clifford - GitHub page
  • Originally written in the early 2000s by Robert Kern
  • Maintained by Alex Arsenovic for many years
  • Now maintained by: pygae (Pythonic Geometric Alegbra Enthusiasts)

 

FOCUSES ON CLEAN SYNTAX AND USEFUL ALGORITHMS

 

THE GOAL IS TO BE EASY TO USE NOT SUPER SUPER FAST (although it is pretty fast too...)

Installation

 

pip install clifford

or

conda install clifford -c conda-forge

 

 

The operators

Syntax Operation
| Symetric inner product
<< Left contraction
^ Outer product
* Geometric product
X(i) Return the section of the multivector X of grade i
X(ei) Return the section of the multivector X for which ei is the pseudo scalar
X[i] Return the i'th coefficient from the multivector X
X.normal() Return the normalised multivector so that X*~X is +- 1
up(x) Maps a multivector from ND euclidean space to its (N+1,1) conformal equivalent point
from clifford.g3c import *

A = up( 4*e1 - 5.1*e2 + 9*e3 ) # Mapping 3DGA to Conformal:  
(4.0^e1) - (5.1^e2) + (9.0^e3) + (61.005^e4) + (62.005^e5)

B = up( -2.2*e1 + 3*e2 + 6.8*e3 ) # Mapping 3DGA to Conformal
-(2.2^e1) + (3.0^e2) + (6.8^e3) + (29.54^e4) + (30.54^e5)

A|B # Symmetric inner product
-54.445

A^B # Outer product
(0.78^e12) + (47.0^e13) + (252.371^e14) + (258.571^e15) - (61.68^e23) - (333.669^e24) - (341.769^e25) - (148.974^e34) - (146.774^e35) + (31.465^e45)

A << (A ^ B) # Asymmetric left contraction
(217.78^e1) - (277.6695^e2) + (490.005^e3) + (3321.41723^e4) + (3375.86223^e5)

A*B # Geometric product
-54.445 + (0.78^e12) + (47.0^e13) + (252.371^e14) + (258.571^e15) - (61.68^e23) - (333.669^e24) - (341.769^e25) - (148.974^e34) - (146.774^e35) + (31.465^e45)

(A * B)(2) # Grade selection
(0.78^e12) + (47.0^e13) + (252.371^e14) + (258.571^e15) - (61.68^e23) - (333.669^e24) - (341.769^e25) - (148.974^e34) - (146.774^e35) + (31.465^e45)

(A * B)(1) # Grade selection
0
 
(A * B)(e123) # Subalgebra selection
-54.445 + (0.78^e12) + (47.0^e13) - (61.68^e23)

(A * B)[0] # Coefficient selection
-54.445000000000164

Geometric primitives in CGA

Grade Construction Interpretation
1 up(x) Point
2 A^B Point pair
3 A^B^C Circle
3 A^B^einf Line
4 A^B^C^D Sphere
4 A^B^C^einf Plane
from clifford.g3c import * # Import Conformal GA (4,1)

from clifford.tools.g3c import * # Import prebuilt tools for conformal GA

random_conformal_point()
(0.3492^e1) + (16.36818^e2) - (1.09622^e3) + (134.12051^e4) + (135.12051^e5)

random_point_pair()
(0.14781^e12) + (0.2147^e13) + (3.91966^e14) + (3.94971^e15) + (0.26825^e23) + (6.25298^e24) + (6.31054^e25) + (1.9694^e34) + (1.99847^e35) + (0.25491^e45)

random_circle()
(0.00893^e123) - (1.58332^e124) - (1.6043^e125) - (4.67362^e134) - (4.73433^e135) - (0.21387^e145) + (2.53838^e234) + (2.57207^e235) - (0.0124^e245) - (0.37947^e345)

random_line()
(0.54733^e124) + (0.54733^e125) + (4.48487^e134) + (4.48487^e135) - (0.29013^e145) - (9.78048^e234) - (9.78048^e235) + (0.53594^e245) - (0.79284^e345)

random_sphere()
(4.85087^e1234) + (4.84582^e1235) + (0.01843^e1245) - (1.01612^e1345) - (0.12664^e2345)

random_plane()
-(14.57447^e1234) - (14.57447^e1235) + (0.75755^e1245) - (0.45902^e1345) + (0.46414^e2345)


Visualisation

Steven De Keninck: ganja.js

PGA, CGA, QCGA

Can visualise custom algebras via OPNS signed distance field rendering

 

Python-Ganja bridge: pyganja

Currently only supports CGA

 


# Import Conformal GA (4,1)
from clifford.g3c import *

# Import prebuilt tools for conformal GA
from clifford.tools.g3c import *

# Import pyganja for visualisation
from pyganja import *

object_array = [
random_conformal_point(),
random_point_pair(),
random_circle(),
random_line(),
random_sphere(),
random_plane() 
]

# The simplest way to use pyganja
draw(object_array, 
     static=False , 
     scale=0.1)

Reflections in CGA

  • Reflections in CGA are carried out by "sandwiching" the object to be reflected
X_2 = PX_1P
# Import Conformal GA (4,1)
from clifford.g3c import *

# Import prebuilt tools for conformal GA
from clifford.tools.g3c import *

# Import pyganja for visualisation
from pyganja import *

# Make a load of stuff
object_array = [
random_conformal_point(),
random_point_pair(), 
random_circle(),
random_line()
]

# Make a plane to reflect it in
P = random_plane() 

# Reflect it all in the plane
object_array2 = [P*X*P 
        for X in object_array]

# Pyganja scene api
gs = GanjaScene()
gs.add_objects(object_array, 
                color=int('AA000000',16), 
                static=False)
gs.add_objects(object_array2, 
                color=int('AAFF0000',16), 
                static=False)
gs.add_objects([P], color=int('0000FF00',16), 
                static=False)
render_cef_script(str(gs), scale=0.1)

Conformal Rotors

Rotors in CGA perform

conformal transformations

 

Rotation

Translation

Dilation

Inversion

X_2 = RX_1\tilde{R}

# Import Conformal GA (4,1)
from clifford.g3c import *

# Import prebuilt tools for conformal GA
from clifford.tools.g3c import *

# Import pyganja for visualisation
from pyganja import *

# Create a random circle
X1 = random_circle()

# Create a random rigid rotor 
R_TR = random_rotation_translation_rotor(maximum_translation=0.25, 
                                         maximum_angle=np.pi/16)

# Create a dilation rotor
R_d = generate_dilation_rotor(0.9)

# Combine the rotors
R = R_TR*R_d

# Apply the rotor taken to successively higher powers to create a list of circles
circle_list = [ (R**i) * X1 * ~(R**i) for i in range(20) ]

# Draw the list of circles
draw(circle_list, static=False , scale=0.1)

Logarithm of rotors

  • In CGA each conformal rotor can be mapped to a bivector with the generalised log function
  • These bivectors can be interpolated, allowing you to interpolate rotors
  • An alternate interpolation technique is the square root of a rotor, we can generalise this to an n'th root
  • Both are implemented in the clifford library
from clifford.tools.g3c import *
from clifford.tools.g3c.rotor_parameterisation import *
from pyganja import *

# Create two objects
X1 = random_circle()
X2 = random_circle()

# Find the rotor between them
R = rotor_between_objects(X1, X2)

# Take the log of the rotor
logR = general_logarithm(R)

# Blend from 0 to 1
rotor_list = [general_exp(0.1*alpha*logR) 
              for alpha in range(10)]

# Apply the interpolated rotors
object_list = [R_int*X1*~R_int 
               for R_int in rotor_list]

# The simplest way to use pyganja
draw(object_list , 
    static=False , 
    scale=0.1)

Direct linear interpolation of conformal objects

  • Adding objects does not in general give a valid object multivector
  • Need to project to the object manifold
  • This can be acheived by considering the form of the added object
X_1 + X_2 = (\alpha + \mu(X_1X_2 + X_2X_1))X_3

This allows us to acheive complex tasks like splining and clustering

from clifford.g3c import *
from clifford.tools.g3c import *
from clifford.tools.g3c.object_clustering import *
from pyganja import *

# Set up parameters for clustering
object_generator = random_circle
n_clusters = 3
n_objects_per_cluster = 10
n_shotgunning = 60

# Generate the objects for clustering
all_objects, object_clusters = generate_n_clusters(object_generator, 
                                                   n_clusters, 
                                                    n_objects_per_cluster)
# Perform the k-means clustering
cluster_result = n_clusters_objects(n_clusters, all_objects,
                                    initial_centroids=None,
                                    n_shotgunning=n_shotgunning,
                                    averaging_method='unweighted')
[new_labels, centroids, start_labels, start_centroids] = cluster_result 

# Visualise the result
gs = GanjaScene()
gs.add_objects([all_objects[i] for i,l in enumerate(new_labels) if l==0], 
               static=False, color=int('00FF0000',16))
gs.add_objects([all_objects[i] for i,l in enumerate(new_labels) if l==1], 
               static=False, color=int('0000FF00',16))
gs.add_objects([all_objects[i] for i,l in enumerate(new_labels) if l==2], 
               static=False, color=int('000000FF',16))
# render_notebook_script(str(gs),scale=0.1)
# render_cef_script(str(gs),scale=0.1)

Saving and Loading MultiVector Arrays

  • Clifford contains functionality for saving arrays of multivectors in a compressed file format .ga based on the open source file format HDF5
  • Also supports an uncompressed JSON output for transmission and decoding over the web
from clifford.g3c import *
from clifford.tools.g3c import *
from pyganja import *

# Make a load of lines in an MVArray
line_array = MVArray( generate_random_object_cluster(10, random_line) )

# Save the array as a .ga file
line_array.save('line_array.ga')

# We can also load .ga files
line_array_loaded = layout.load_ga_file('line_array.ga')

Summary

  • Clifford is simple and easy to use
  • The syntax is close to the mathematics
  • Pyganja is compatible visualisation tool for CGA