# Conformal Geometric Algebra with Python

Part 1

## 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) # Coefficient selection
-54.445000000000164

## Geometric primitives in CGA

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()
color=int('AA000000',16),
static=False)
color=int('AAFF0000',16),
static=False)
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)

• 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