Ivory: Geometric Units
In this page we will leave the ordinary number line, and begin to
introduce the geometric
level of the Ivory tower. Our
starting point is the equation:
a2 + 1 = 0
Or, as an s-expression (where the
sqr
function multiplies a number by itself):
= (+ (sqr a) 1)
(0)
This looks simple enough, but let’s take a moment to consider its
broader meaning. In particular, the sum includes one quantity that’s
squared (sqr a)
and one
quantity that isn’t squared 1
. This feels
“off”, for a couple of reasons:
- As a physicist: the variable
a
must have different units than the constants1
and0
in order to be dimensionally consistent. For example, ifa
were a length then the constants must be areas. - As a computer scientist: this feels like an ill-typed expression, like we’re mixing up encodings of semantically-distinct quantities.
Sure, those fears might be unfounded; but we can put
ourselves at ease by re-stating the equation entirely with squared
terms. This requires introducing a couple of extra variables, which
we’ll call b
and c
:
= (+ (sqr a) (sqr b))
( (sqr c))
Now let’s solve this equation:
- We know that
(= (sqr b) 1)
and(= (sqr c) 0)
, by definition. - We can rearrange the equation to find that
(= (sqr a) (- (sqr b)))
and hence(= (sqr a) -1)
You may be tempted to “square root” these and say that (= b 1)
,
(= c 0)
and (if you’re familiar with complex numbers) (= a i)
;
however, those are just some of the possible solutions to these
equations. Not only are there negative solutions too, but Geometric
Algebra provides even more by extending our arithmetic to include
extra numbers! These come in three flavours, one for each of
our variables (apologies for the intimidating names, which actually
pre-date Geometric Algebra!):
- We’ll call solutions to
(= (sqr b) 1)
(other than1
and-1
) hyperbolic units and write them ash₀
,h₁
,h₂
, etc. - We’ll call solutions to
(= (sqr c) 0)
(other than0
) dual units and write them asd₀
,d₁
,d₂
, etc. - We’ll call solutions to
(= (sqr a) -1)
imaginary units and write them asi₀
,i₁
,i₂
, etc.
Practical applications of GA will only use a few of these units, but
I want my code to support arbitrarily-many. Each of these units is a
perfectly legitimate number
, but
they are not part of rational
; hence they must occur at a
higher level of our numerical tower. We’ll define a new level called
geometric
to contain all of them.
I’ll be referring to them as GA/geometric
/non-rational
units”. Note that we
cannot call them “irrational”, since that already
means something else!
These non-rational
numbers do
not appear on the familiar number line. We’ll
give their geometric interpretation later. For now we’ll just treat them
as symbolic constants, the same way we treat τ, 𝑒, ϕ, etc.
Representing Geometric Units In Scheme
This is pretty simple, since each unit contains two pieces of
information: the flavour and the index. We’ll represent the flavour
using a symbol: either h
or d
or i
. The index will just be a number
(we’ll be sticking to natural
indexes, but won’t enforce it).
We’ll combine these into a pair, by either giving them as
inputs to the cons
operation,
like (cons 'd 0)
for d₀
; or with a quotation, like
'(i . 2)
for i₂
(where the .
makes this a pair, rather than a list).
It looks like we’re calling functions named d
, h
and i
with a number
as input; but for something to
be a name, there must be some underlying definition that it’s referring
to. In this case we have no definitions (or, if you prefer, symbols are
merely names for themselves). These are “uninterpreted
functions”, meaning Racket will just pass around these expressions
as-is.
It may feel like cheating to claim these values are “incorporated
deeply” into the language, compared to “usual” numbers. Admittedly the
natural
type is a special case
(due to its place-value notation), but it turns out that all of
Scheme’s standard numerical tower relies on this “uninterpreted
function” trick!
Consider integer
: this
includes both natural
numbers and
their negatives. The latter are represented by prefixing the former with
a -
symbol, representing negation, which is left uninterpreted. The higher
levels, rational
and complex
, use uninterpreted functions
with two inputs (numerator & denominator, for rational
; “real” & “imaginary” for
complex
).
In any case, here are some Scheme functions for manipulating these GA units:
;; Predicates for spotting if a value is a GA unit (i.e. an appropriate pair)
(define/match (unit-d? n)[((cons 'd index)) (number? index)]
[(_) #f])
(define/match (unit-h? n)[((cons 'h index)) (number? index)]
[(_) #f])
(define/match (unit-i? n)[((cons 'i index)) (number? index)]
[(_) #f])
define unit-ga? (disjoin unit-h? unit-d? unit-i?))
(
;; Functions to access the flavour and index of a GA unit. The 'car'/'cdr'
;; functions return the first/second element of a pair.
define unit-flavour car)
(define unit-index cdr)
(
;; Helper for sorting units alphabetically
define (unit<? x y)
(let ([fx (unit-flavour x)]
([fy (unit-flavour y)]
[ix (unit-index x)]
[iy (unit-index y)])
or (symbol<? fx fy)
(and (equal? fx fy) (< iy ix))))) (
Parsing Geometric Units
Now we have a representation for geometric
units, as a pair of flavour and index, we can define a function to read
this subscript syntax:
define (subscripts-to-digits s)
([digits s])
(for/fold ([i "0123456789"]
([char "₀₁₂₃₄₅₆₇₈₉"])
string char) (string i))))
(string-replace digits (
define (parse-unit in)
(;; Look for flavour and index, using regexp capture groups
(match (regexp-match;; Match flavour then index; disallow leading zeros
"^([dhi])((₀(?![₀₁₂₃₄₅₆₇₈₉]))|([₁₂₃₄₅₆₇₈₉][₀₁₂₃₄₅₆₇₈₉]*))"
#px
in);; No match
[#f #f]
;; Found a match: s is entire match, groups are captured substrings
[(cons s (cons flavour-group (cons index-group _)))
cons
(string->symbol (bytes->string/utf-8 flavour-group))
(string->number
(])) (subscripts-to-digits (bytes->string/utf-8 index-group))))
Code
Test results
raco test: (submod "geo-units.rkt" test)
29 tests passed