Conformal geometry with Clifford algebra

knitr::opts_chunk$set(echo = TRUE)
library("clifford")
knitr::include_graphics(system.file("help/figures/clifford.png", package = "clifford"))

\newcommand{\grad}[2]{\left\langle #1\right\rangle_{#2}} \newcommand{\ak}{\grad{\mathbf{A}}{k}} \newcommand{\ba}{\mathbf{a}} \newcommand{\bb}{\mathbf{b}} \newcommand{\bn}{\mathbf{n}} \newcommand{\bA}{\mathbf{A}} \newcommand{\bS}{\mathbf{S}} \newcommand{\bX}{\mathbf{X}} \newcommand{\bB}{\mathbf{B}} \newcommand{\bx}{\mathbf{x}} \newcommand{\by}{\mathbf{y}}

\newcommand{\einf}{\mathbf{e}_\infty} \newcommand{\ezero}{\mathbf{e}_0}

To cite the clifford package in publications please use @hankin2025_clifford_rmd. This short document shows how conformal geometry may be implemented using the clifford R package; it follows @hildenbrand2013 and @perwass2009. Here we work in $\operatorname{Cl}(p,q)$, which Perwass denotes $\mathbb{G}{p,q}$. The set of grade-$k$ objects is denoted $\mathbb{G}{p,q}^k$. First we define the IPNS and OPNS [inner product null space and outer product null space] of $\mathbf{A}\in\mathbb{G}_{p,q}^k$.

$$ \begin{eqnarray} \operatorname{IPNS}(\mathbf{A}) &=& \left\lbrace\bx\in\mathbb{G}^1_{p,q}\colon\bx\cdot\mathbf{A} = 0\right\rbrace\ \operatorname{OPNS}(\mathbf{A}) &=& \left\lbrace\bx\in\mathbb{G}^1_{p,q}\colon\bx\wedge\mathbf{A} = 0\right\rbrace \end{eqnarray} $$

Thus if $\mathbf{A}=\ak=\bigwedge_{i=1}^k\ba_i$, then $\operatorname{OPNS}(\mathbf{A})=\operatorname{span}(\ba_1,\ldots,\ba_k)\subseteq\mathbb{G}_{p,q}$. Further, $\operatorname{OPNS}(\mathbf{A})$ is linear in the sense that $\bx,\by\in\operatorname{OPNS}(\mathbf{A})$ implies $\alpha\bx+\beta\by\in\operatorname{OPNS}(\mathbf{A})$ for any real numbers $\alpha,\beta$. We will use this system to express a geometrical object $\mathcal{G}\subset\mathbb{R}^3$ (such as a sphere or a line) as a point $\mathbf{P}$ in $\operatorname{Cl}(4,1)$; the idea is that the IPNS or OPNS of $\mathbf{P}$ is $\mathcal{G}$. This allows one to express geometrical ideas in pure Clifford formalism, without needing to take a dangerous and unsightly basis.

To work in R, we set up some basic features of conformal geometry. Specifically, we consider the three Euclidean basis vectors $\mathbf{e}1$, $\mathbf{e}_2$, $\mathbf{e}_3$ together with two additional basis vectors $\mathbf{e}+$ and $\mathbf{e}-$ obeying $\mathbf{e}+^2=1$, $\mathbf{e}-^2=0$, $\mathbf{e}+\cdot\mathbf{e}_-=0$. If we define

[ \ezero=\frac{1}{2}\left(\mathbf{e}--\mathbf{e}+\right),\qquad \einf =\mathbf{e}- +\mathbf{e}+ ]

then we may say that $\ezero$ represents "the origin" and $\einf$ represents "the point at infinity". We see that $\ezero^2=\einf^2=0$ and $\einf\cdot\ezero=-1$; the geometric product is given by $\einf\ezero=-E-1$ and $\ezero\einf=E-1$ where $E=\einf\wedge\ezero$. It is straightforward to implement these objects using the clifford package:

dimension <- 3
options("maxdim" = dimension+2)  # paranoid safety measure
signature(dimension + 1,1)
eplus <- basis(dimension+1)
eminus <- basis(dimension + 2)

e0 <-  (eminus - eplus)/2
einf <- eminus + eplus
E <- e0 ^ einf

So

e0
einf
E

Points

With these definitions, we can consider Euclidean vectors $\ba,\bb\in\mathbb{R}^3$ and conformal embeddings $\bA,\bB$ given by

[ \bA=C(\ba)=\ba+\ba^2\einf/2 +\ezero\qquad\bB=C(\bb)=\bb+\bb^2\einf/2 +\ezero ]

This is straightforward in package idiom:

point <- function(x){ as.1vector(x) + sum(x^2)*einf/2 + e0 }

Thus point() takes an R vector of length 3 and returns its conformal embedding. For example, we may translate points $(1,2,5)$ and $(2,2,2)$ to their conformal equivalent:

a <- c(1,2,5)
b <- c(2,2,2)
point(a)
point(b)

It can be shown that $\bA\cdot\bB=-\left\lVert\ba-\bb\right\rVert^2/2$ [where the dot product is the Clifford inner product, %.%]. Package idiom to verify this would be

c(conformal=drop(point(a) %.% point(b)), Euclidean = -sum((a-b)^2)/2)

showing that the two results match.

Sphere, IPNS

We can define a sphere with center $\ba$ and radius $\rho$ as

[ \bS=C(\ba) - \rho^2\einf/2 ]

and then the sphere is just the inner product null space of $\bS$, that is $\left\lbrace\bx\colon\bS\cdot\bx=0\right\rbrace$. This is straightforward to implement computationally. Suppose we have a sphere of radius 5, center $(1,2,3)$:

sphere <- function(x,r){ point(x) - r^2*einf/2}
S <- sphere(1:3,5)  # center (1,2,3) radius 5:
S

Then object S is a conformal representation of such a sphere. The radius $\rho$ can be calculated from $\bS^2=\rho^2$. Package idiom:

drop(S^2)   # 5^2 = 25

Finding the center of the sphere is slightly more involved; Hildenbrand calls this the sandwich product given by $P=\bS\einf\bS$:

S*einf*S

Hildenbrand shows that the scaling factor is $-2$ so this gives us

-S*einf*S/2

which we recognise as the point $(1,2,3)$:

point(1:3)

Sphere, OPNS

We can also consider the OPNS for a sphere, defined in terms of four points that lie on it. For example, if our four points are $(0,0,0)$, $(1,0,0)$, $(0,1,0)$, $(0,0,1)$ we would have a rather involved algebraic calculation resulting in a sphere of radius $\frac{\sqrt{3}}{2}$ and center $\left(\frac{1}{2},\frac{1}{2},\frac{1}{2}\right)$.

The conformal representation for a sphere in OPNS would be

[ S^*=P_1\wedge P_2\wedge P_3\wedge P_4 ]

and points that lie on the sphere are the set

[\left\lbrace\bx\colon\bx\wedge S^*=0\right\rbrace.]

Observe again that this parameterization does not require one to take a basis of $\mathbb{R}^3$, as all the results are presented in terms of vector quantities: at no point does one need to consider components or elements of any vector. The R idiom for defining the sphere touching $\left\lbrace P_1,P_2,P_3,P_4\right\rbrace$ is straightforward:

origin <- point(c(0,0,0))
px <- point(c(1,0,0))
py <- point(c(0,1,0))
pz <- point(c(0,0,1))

(S <- origin ^ px ^ py ^ pz)

And this is the representation for a sphere (translating this into a center and radius is not yet implemented). Slightly slicker R idiom might be:

spherestar <- function(...){Reduce(`^`,list(...))}
spherestar(origin, px, py, pz)

As a verification, we may check that point $\left(\frac{1}{2},\frac{1}{2},\frac{1}{2}+\frac{\sqrt{3}}{2}\right)$ is on the sphere as well:

p <- point(c(1,1,1+sqrt(3))/2)
Mod(p ^ S)

Above we see that $p\wedge S$ is zero to within numerical error. Again observe that this is established without taking a basis of $\mathbb{R}^3$.

Planes

A plane is defined as $\hat{\bn} + d\einf$, where $\hat{\bn}$ is the unit normal and $d$ the distance to the origin.

plane <- function(n,d){ as.1vector(n/sqrt(sum(n^2))) + d*einf}

For example, we consider the plane $\Pi$ with normal $\mathbf{n}=\left(1,2,5\right)$ and distance 7. We may use plane() above but first need to calculate $\hat{\mathbf{n}}=\mathbf{n}/\left|\mathbf{n}\right|$:

n <- c(1,2,5)
nhat <- n/sqrt(sum(n^2))
d <- 7
Pi <- plane(nhat,d)
Pi

Just to check, we may verify that some points known to lie in $\Pi$ are in fact in its IPNS. We observe that the point $7\hat{\mathbf{n}}\in\Pi$, and also that vectors $\mathbf{u}=(2,-1,0)$ and $\mathbf{v}=(5,0,-1)$ are orthogonal to the normal of $\Pi$. So $7\hat{\mathbf{n}} + \alpha\mathbf{u} + \beta\mathbf{v}\in\Pi$ for any real numbers $\alpha,\beta$. In R:

u <- c(2,-1,0)
v <- c(5,0,-1)
P1 <- point(d*nhat)                
P2 <- point(d*nhat + 1.3*u + 3.44*v)
P3 <- point(d*nhat - 6.1*u + 1.02*v)

Above we use some made-up values of $\alpha,\beta$ to generate P1, P2, P3 which are known to lie in plane $\Pi$. Then to verify that these points lie in the IPNS of $\Pi$ we need to take the inner product with the conformal representation of $\Pi$:

c(drop(Pi %.% P1),drop(Pi %.% P2),drop(Pi %.% P3))

Above we see zero (to numerical precision), showing that we do indeed have $7\hat{\bn} + \alpha\mathbf{u} + \beta\mathbf{v}\in\operatorname{IPNS}(\Pi)$ for at least these values of $\alpha$ and $\beta$. Alternatively, a plane can be thought of as a sphere that touches the point at infinity; thus the OPNS of plane is given by

[ \pi^\star = P_1\wedge P_2\wedge P_3\wedge\einf ]

where $P_1,P_2,P_3$ are any points that lie in the plane. To illustrate this we will use the three points used in the IPNS above:

Pi2 <- P1 ^ P2 ^ P3 ^ einf

Then for verification we can create another point known to be in the plane and check that this is in the OPNS. Below we will use p4 which is $d\hat{\mathbf{n}} + 7.6\mathbf{u} - 9.23\mathbf{v}$:

p4 <- point(d*nhat + 7.6*u - 9.23*v)
Mod(Pi2 ^ p4)

Above we see a very small result showing that p4 is indeed in the OPNS of plane Pi2.

Circle

A circle is defined as the intersection of two spheres:

circle <- function(S1,S2){  # IPNS
    S1 ^ S2
}

For example

circle(sphere(1:3,5),sphere(c(1.1,2.1,3.4),6))

A circle may be represented in the OPNS by specifying three points that lie on it:

[ Z^*=P_1\wedge P_2\wedge P_3]

circlestar <- function(...){  # OPNS; A^B^C
    jj <- list(...)
    stopifnot(length(jj) == dimension)
    Reduce(`^`,lapply(jj,point))
}

(CIRC <- circlestar(c(1,2,3),c(5,6,3),c(8,8,-2)))

verify:

M <- rbind (c(1,2,3),c(5,6,3),c(8,8,-2))
badness_center <- function(pos){
  bad1 <-  sd(c(
  sum((pos-M[1,])^2),
  sum((pos-M[2,])^2),
  sum((pos-M[3,])^2)))   # pos should be equidistant from rows of M
  bad2 <- abs(det(sweep(M,2,pos))) # pos should be collinear with rows of M
  return(bad1+bad2)
  }

center <- nlm(badness_center,c(0,0,0))$estimate
badness_pointoncircle <- function(pos){ # pos is a point [that should be] on the circle
bad1 <-  var(c(sum((center-M[1,])^2), sum((center-M[2,])^2), sum((center-M[3,])^2),   sum((center-pos  )^2)))
bad2 <-  abs(det(sweep(M,2,pos))) # pos should be coplanar with M[i,]
return(bad1+bad2)
}

poc <- point(nlm(badness_pointoncircle,c(0,0,0))$estimate)
poc  # point on circle, found numerically [chunk omitted]
poc ^ CIRC

Above we see that poc is at least close to the circle from the small magnitude of the terms in the wedge product.

Lines and point pairs

A line is the intersection of two planes; in R:

line <- function(P1,P2){ P1 ^ P2 }

and a "point pair" is the intersection of three spheres; in R:

pointpair <- function(S1,S2,S3){ S1 ^ S2 ^ S3 }

It is not at all obvious that three spheres intersect in a pair of points; and still less obvious that the process is associative. However, we may verify associativity explicitly:

S1 <- sphere(c(3,2,4),3)
S2 <- sphere(c(3,1,4),4)
S3 <- sphere(c(1,3,3),3)
(S1^S2)^S3
(S1^S2)^S3 == S1^(S2^S3)
options("maxdim" = NULL) # restore default

References



Try the clifford package in your browser

Any scripts or data that you put into this service are public.

clifford documentation built on July 5, 2026, 5:07 p.m.