Skip to content

Latest commit

 

History

History
511 lines (338 loc) · 9.26 KB

File metadata and controls

511 lines (338 loc) · 9.26 KB

Geometric Algebra Implementation

Purpose

This document explains how geometric algebra was implemented in Bifourcation from the first useful version to the current version.

The goal was not to make a full geometric algebra library. The goal was narrower:

build just enough 2D geometric algebra
to make Fourier drawing feel like rotation in an oriented plane
instead of rotation in an unexplained imaginary axis

The final implementation uses the 2D Euclidean algebra generated by:

1
e₁
e₂
e₁e₂

The central object is the bivector:

B = e₁e₂

This bivector represents the oriented drawing plane.

Because:

B² = -1

it can do the job that i usually does in complex Fourier analysis, while still having a concrete geometric meaning.


The first version

The first version was essentially a minimal multivector representation.

A point on the canvas was represented as a vector:

v = x e₁ + y e₂

Internally, a multivector needed four scalar components:

type Multivector = {
  scalar: number;
  e1: number;
  e2: number;
  e12: number;
};

This was enough to represent:

scalar values
vectors
bivectors
mixed multivectors

The very first requirement was to support the geometric product. Everything else could be derived from that.

The multiplication rules were:

e₁² = 1
e₂² = 1
e₁e₂ = -e₂e₁
(e₁e₂)² = -1

That gave the app the key behavior it needed:

multiplication by e₁e₂ turns a vector by 90° in the drawing plane

The first geometric-algebra layer was therefore deliberately small:

ZERO
addMultivectors
scaleMultivector
geometricProduct
vector

That was enough to make the Fourier code work without importing a symbolic algebra package.


Why I did not use a general GA library

A general-purpose geometric algebra library would have been more complete, but it would also have hidden the educational point.

The project needed the implementation to be readable. I wanted the code itself to show the basis blades and the product rules.

The trade-off was:

general library:
  more complete
  less transparent
  more dependency weight

small local implementation:
  less general
  easier to inspect
  clearer educational value

The local implementation won because this app only needs the 2D drawing plane.

Higher-dimensional geometric algebra was intentionally left out. There was no need for e₃, trivectors, conformal models, or general blade operations.


The even subalgebra

The next step was introducing the even subalgebra.

A 2D vector point starts as:

v = x e₁ + y e₂

Multiplying by e₁ maps it into the scalar-plus-bivector plane:

e₁v = e₁(xe₁ + ye₂)
    = x e₁² + y e₁e₂
    = x + y e₁e₂

So the usual complex representation:

x + iy

becomes:

x + y e₁e₂

This is the key interpretation:

the imaginary unit is replaced by the oriented drawing plane

The even subalgebra used by the project is:

span{1, e₁e₂}

This space behaves like the complex numbers, but the second basis element is geometric:

e₁e₂ = the oriented drawing plane

The implementation therefore added helpers for converting between canvas vectors and even multivectors:

vectorToEvenMultivector(v)
evenMultivectorToVector(z)

The first helper maps:

x e₁ + y e₂

to:

x + y e₁e₂

The second maps the scalar-plus-bivector signal back to canvas vector coordinates.


Rotors

The rotor helper was then added in the even subalgebra layer.

The rotor is:

R(θ) = e^(Bθ)

where:

B = e₁e₂

Since:

B² = -1

the exponential expands to:

e^(Bθ) = cos(θ) + B sin(θ)

So in code, the rotor is simple:

rotor(angle) = {
  scalar: Math.cos(angle),
  e1: 0,
  e2: 0,
  e12: Math.sin(angle),
}

That may look like a complex exponential, but the interpretation is different:

cos θ + i sin θ

becomes:

cos θ + e₁e₂ sin θ

The plane of rotation is no longer implicit. It is the bivector e₁e₂.


The half-angle question

One subtle issue came up later: in geometric algebra, vector rotation is usually written as the sandwich product:

v' = RvR⁻¹

with:

R = e^(-Bθ/2)

That is correct for the general rotor action on raw vectors.

Bifourcation uses a related but more specialized 2D plane-phase action. The Fourier signal is treated in the scalar-plus-bivector phase plane, and phase advancement is one-sided:

z' = z e^(Bθ)

This is why the Fourier phase uses the full angle:

θ = 2πkt

rather than the half-angle sandwich formula.

The final interpretation is:

general GA vector rotation:
  v' = RvR⁻¹
  R = e^(-Bθ/2)

Bifourcation's 2D Fourier phase action:
  z' = z e^(Bθ)

Both are legitimate. The project uses the second because it is a 2D Fourier drawing system.


Visualizing bivectors

The next major update was visual. The project originally showed circular Fourier components, but the goal became more explicitly geometric-algebraic.

The question was how to make the bivector visible without misleading users.

The final decision was to support three visual modes:

Disk
Blade
Companion

Each mode explains a different part of the same mathematical structure.


Disk view

Disk view emphasizes rotational symmetry.

A Fourier term has constant magnitude while its phase changes. Its endpoint traces circular motion. Disk mode therefore represents the current rotor-driven term with an equal-area disk glyph.

If the current component vector has length:

|r|

then the associated oriented area magnitude is:

|r|²

A disk with radius R has area:

πR²

So the radius is chosen as:

R = |r| / √π

The disk is not the rotor itself.

The rotor is the action:

Rₖ(t) = e^(e₁e₂ · 2πkt)

The disk is a glyph for the current rotor-driven term.

The arrows on the disk mark phase/orientation. They do not mean the plane e₁e₂ itself is rotating.


Blade view

Blade view is the most direct oriented-area view.

Given a component vector:

r

the app draws its e₁e₂-rotated companion:

e₁e₂r

Because e₁e₂ is a unit plane element, the companion has the same length and is perpendicular to r.

So the blade spanned by:

r
e₁e₂r

appears as a square.

That does not mean bivectors are squares. A bivector is oriented area. The square is a representative of the oriented area generated by that vector and its companion.

This distinction matters. The final wording became:

Blade mode represents each component's oriented area using the square spanned by the component vector and its e₁e₂-rotated companion.

Companion view

Companion view shows the action directly:

r → e₁e₂r

This view avoids filled areas and instead shows the primary vector and its perpendicular companion vector.

It is useful because it makes the role of e₁e₂ explicit:

e₁e₂ acts as a 90° turn in the drawing plane

Orientation cues

Positive and negative frequencies are drawn in the stroke color because they belong to the same stroke.

Opposite orientation is shown with visual texture:

negative blades:
  diagonal hatching

negative disks:
  diagonal hatching

negative companion vectors:
  dashed companion line

Arrows show direction of phase/orientation.

This was chosen over using totally different colors because color already carries stroke identity.


Rotor labels

Small rotor labels were added to make the algebra visible inside the animation.

The compact label is:

cₖRₖ(t)

The expanded meaning is:

cₖe^(e₁e₂ · 2πkt)

The labels are intentionally small. They should remind the viewer what is happening without dominating the drawing.

The final interpretation is:

each visible component is a Fourier coefficient advanced by a rotor in the e₁e₂ plane

Final geometric-algebra decisions

The final implementation keeps these decisions:

Use a minimal local 2D GA implementation.
Represent canvas points as vectors.
Map vectors into the scalar-plus-bivector even subalgebra for Fourier phase.
Use e₁e₂ as the oriented drawing plane.
Use e^(e₁e₂θ) as the rotor / phase action.
Visualize components with disk, blade, and companion glyphs.
Keep the glyphs honest: they are not separate rotating planes.

The final mental model is:

There is one drawing plane: e₁e₂.
There are many Fourier terms advanced in that plane.
The disks, blades, and companions are visual glyphs for the current terms.
The rotor is the action that advances each term through phase.

What I would avoid saying

These statements are avoided because they can mislead:

the disk is the rotor
the bivector plane rotates
each term has its own separate plane
bivectors are squares

Better statements are:

the rotor acts in the e₁e₂ plane
the disk is an equal-area glyph for the current term
the blade is an oriented-area representative
the arrows mark term phase/orientation

That is the final geometric-algebra framing used by Bifourcation.