GA-FuL Design Principles
Table of Contents
- Overview
- Core Design Intentions (CDIs)
- Data-Oriented Programming (DOP)
- Implementation of DOP Principles
- Practical Examples
Overview
The design of GA-FuL is based on two main concepts:
- Core Design Intentions (CDIs): Five main goals that the system should fulfill
- Data-Oriented Programming (DOP): Four principles for practical implementation
These principles emerged from experience with the predecessor system GMac and address its weaknesses.
Core Design Intentions (CDIs)
CDI-1: Abstraction of Multivector Operations
Goal: Separation of multivector operations and concrete scalar representations
Problem:
- Scalars can be represented in many ways:
- IEEE 754 floating point numbers (32-bit, 64-bit)
- Arbitrary precision decimal numbers
- Rational numbers (fractions)
- Symbolic expressions (for CAS)
- Multidimensional arrays (for ML/AI)
- Sampled signals (for signal processing)
Solution:
- Generic implementation via
IScalarProcessor<T> - All GA operations independent of scalar type
- Easy integration of new scalar types
Example:
// The same code works with all scalar types
var processor = XGaProcessor<T>.CreateEuclidean(scalarProcessor);
var v1 = processor.CreateComposer()
.SetVectorTerm(0, a)
.SetVectorTerm(1, b)
.SetVectorTerm(2, c)
.GetVector();
var v2 = processor.CreateComposer()
.SetVectorTerm(0, x)
.SetVectorTerm(1, y)
.SetVectorTerm(2, z)
.GetVector();
var result = v1.Gp(v2); // Geometric Product
// T can be: double, float, decimal, BigDecimal, Expr, etc.
Benefits:
- ✓ One code for all scalar types
- ✓ Easy addition of new scalar types
- ✓ Maximum reusability
CDI-2: Reduction of Memory Requirements
Goal: Efficient storage of sparse multivectors in high-dimensional GAs
Problem:
- A full multivector in $n$-dimensional GA requires $2^n$ scalars
- A full $k$-vector requires $\binom{n}{k}$ scalars
- Examples (with 32-bit floats):
- 30D GA, full multivector: 8 GB
- 30D GA, full 15-vector: 1.16 GB
- This is impractical for most applications
Reality:
- Practical GA applications mostly use sparse multivectors
- Example: 5D-CGA can represent 3D geometry with only 5 out of 32 scalars
Solution:
- Dictionary-based storage:
IReadOnlyDictionary<IIndexSet, T> - Only non-null components are stored
- Automatic selection of the most efficient data structure
Memory Hierarchy:
| Multivector Type | Data Structure | Memory Efficiency |
|---|---|---|
| Zero-Multivector | EmptyDictionary |
0 scalars |
| Single-Component | SingleItemDictionary |
1 scalar + index |
| Sparse | Dictionary<IIndexSet, T> |
n scalars + indices |
| Dense (small) | Lookup tables | Optimized for small n |
Index-Set Hierarchy:
| Dimension Range | IIndexSet Implementation | Memory |
|---|---|---|
| Empty set | EmptyIndexSet |
0 bytes |
| 1 element | SingleIndexSet |
4 bytes |
| < 64 dimensions | UInt64IndexSet |
8 bytes |
| Arbitrary (dense) | DenseIndexSet |
Array of ints |
| Arbitrary (sparse) | SparseIndexSet |
HashSet |
Example:
// 100D-GA, sparse multivector with 10 components
var composer = processor.CreateMultivectorComposer();
composer.SetTerm(indexSet1, scalar1);
composer.SetTerm(indexSet2, scalar2);
// ... 10 terms total
var mv = composer.GetMultivector();
// Memory: ~10 scalars + 10 index-sets
// Full multivector would require 2^100 scalars!
Benefits:
- ✓ High-dimensional GAs become practical
- ✓ Automatic optimization
- ✓ Flexible dimensionality
CDI-3: Metaprogramming Capabilities
Goal: Code generation from GA expressions for optimized performance
Problem:
- Generic implementation is flexible but sometimes slow
- High-performance applications need optimized code
- Manual writing of optimized code is error-prone
Solution:
- Automatic code generation from GA expressions
- Optimization through:
- Constant propagation
- Common subexpression elimination
- Symbolic simplification
- Genetic programming
- Support for multiple target languages
Workflow:
High-Level GA Operations
↓
Expression Tree
↓
Optimization
↓
Target Language Code (C++, C#, CUDA, etc.)
Example:
// 1. Define computations in GA-FuL
var context = new MetaContext();
var x = context.CreateParameter("x");
var y = context.CreateParameter("y");
var scalarProcessor = context.ScalarProcessor;
var processor = XGaProcessor<IMetaExpression>.CreateEuclidean(scalarProcessor);
var v1 = processor.CreateComposer()
.SetVectorTerm(0, x)
.SetVectorTerm(1, y)
.SetVectorTerm(2, 0)
.GetVector();
var v2 = processor.CreateComposer()
.SetVectorTerm(0, 1)
.SetVectorTerm(1, 1)
.SetVectorTerm(2, 1)
.GetVector();
var result = v1.Gp(v2);
// 2. Optimize
context.Optimize();
// 3. Generate C++ code
var cppCode = new CppCodeComposer().Generate(context);
// Result: Highly optimized C++ code
Use Cases:
- CUDA code for GPU acceleration
- Embedded systems (C/C++)
- Web applications (JavaScript)
- Scientific computing (MATLAB/Python)
Benefits:
- ✓ Best performance for production code
- ✓ Error-free code generation
- ✓ Automatic optimization
- ✓ Multiple target languages
CDI-4: Layered System Design
Goal: Organization of complexity through layers for different user groups
Problem:
- CDI-1 to CDI-3 lead to high complexity
- Different users have different needs
- Some need abstract high-level API
- Others need low-level control for performance
Solution: Four layers with increasing abstraction:
Abstraction Level
↑
│ ┌────────────────────────────┐
High │ │ Metaprogramming Layer │
│ ├────────────────────────────┤
│ │ Modeling Layer │
│ ├────────────────────────────┤
│ │ Algebra Layer │
│ ├────────────────────────────┤
Low │ │ System Utilities Layer │
│ └────────────────────────────┘
User Profiles:
| User Type | Preferred Layer | Use Case |
|---|---|---|
| Researcher | Modeling | Prototyping geometric algorithms |
| Software Developer | Metaprogramming | Code generation for production |
| Library Developer | Algebra | Extending functionality |
| Performance Engineer | Algebra + Utilities | Optimizing critical paths |
Examples:
High-Level (Modeling):
// Coordinate-free, intuitive API
var point = cga.Encode.IpnsRound.Point(x, y, z);
var sphere = cga.Encode.IpnsRound.RealSphere(radius, cx, cy, cz);
var intersection = point.Op(sphere);
Low-Level (Algebra):
// Direct control over scalars and basis blades
var blade = processor.CreateBasisBlade(indexSet);
var scalar = scalarProcessor.Times(a, b);
var term = processor.CreateTerm(blade, scalar);
Benefits:
- ✓ Every user finds suitable abstraction level
- ✓ Clear separation of responsibilities
- ✓ Easy maintenance and extension
CDI-5: Unified, Generic, Extensible API
Goal: Unified API for different application classes
Requirements:
- Unified: Consistent naming conventions and patterns
- Generic: Works with all scalar types and GA metrics
- Extensible: Easy addition of new features
Implementation:
1. Consistent Naming Conventions:
// All processors follow the same pattern
IScalarProcessor<T>
XGaProcessor<T>
XGaConformalSpace<T>
// All composers follow the same pattern
XGaMultivectorComposer<T>
XGaKVectorComposer<T>
2. Generic Types:
// One template for all types
XGaMultivector<double>
XGaMultivector<float>
XGaMultivector<Expr> // Symbolic
XGaMultivector<BigDecimal>
3. Extension Methods:
// Unified API through extension methods
multivector.Gp(other) // Geometric Product
multivector.Op(other) // Outer Product
multivector.Lcp(other) // Left Contraction Product
multivector.Rcp(other) // Right Contraction Product
multivector.Sp(other) // Scalar Product
multivector.Reverse() // Reverse
multivector.GradeInvolution() // Grade Involution
4. Builder Pattern:
// Fluent API for construction
var mv = processor.CreateMultivectorComposer()
.SetTerm(index1, scalar1)
.SetTerm(index2, scalar2)
.AddTerm(index3, scalar3)
.GetMultivector();
Supported Application Classes:
- Numerical computations
- Symbolic mathematics
- Signal processing
- Computer vision
- Robotics
- Visualization
- Code generation
Benefits:
- ✓ Easy to learn
- ✓ Consistent across all modules
- ✓ IntelliSense-friendly
- ✓ Easy to extend
Data-Oriented Programming (DOP)
Traditional OOP led to excessive complexity in GA-FuL. DOP offers a better alternative.
Why DOP instead of classic OOP?
Problems with classic OOP:
- ❌ Deep coupling of data and behavior
- ❌ Complex inheritance hierarchies
- ❌ Difficult maintenance in large systems
- ❌ Encapsulation can lead to inflexibility
Benefits of DOP:
- ✓ More readable code
- ✓ More maintainable code
- ✓ More extensible code
- ✓ Better testability
DOP Principle 1: Separation of Behavior and Data
Rule: Keep behavior code and data separate
In OOP:
// Classic OOP: Data and behavior coupled
class Multivector {
private Dictionary<IIndexSet, T> data;
public Multivector Gp(Multivector other) {
// Behavior in the same object
}
}
In GA-FuL (DOP):
// DOP: Data in thin wrapper
class XGaMultivector<T> {
public IReadOnlyDictionary<int, XGaKVector<T>> Data { get; }
}
// Behavior in static extension methods
static class XGaMultivectorExtensions {
public static XGaMultivector<T> Gp<T>(
this XGaMultivector<T> mv1,
XGaMultivector<T> mv2
) {
// Behavior separate from data
}
}
Benefits:
- ✓ Data can be used by multiple behavior modules
- ✓ Behavior can be tested independently
- ✓ Easier code reviews
DOP Principle 2: Generic Data Structures
Rule: Use general data structures instead of special classes
Preferred Structures:
- Arrays/Lists for sequential data
- Dictionaries/Maps for key-value pairs
- Sets for unique collections
- Queues for FIFO
- Trees for hierarchical data
In GA-FuL:
Multivectors:
// NOT a special class with internal array structure
// BUT generic dictionary
IReadOnlyDictionary<int, XGaKVector<T>>
k-Vectors:
IReadOnlyDictionary<IIndexSet, T>
Example:
// Access is simple and transparent
var grade2Part = multivector[2]; // Dictionary access
var scalar = kVector[indexSet]; // Dictionary access
// No hidden internal structures
Benefits:
- ✓ Transparent data structures
- ✓ Standard operations available
- ✓ Easy to debug
- ✓ Familiar to all developers
DOP Principle 3: Immutable Data
Rule: Never modify data directly, but create new versions
Why Immutability?
- Thread safety
- Predictable behavior
- Easier debugging
- Functional programming style
Implementation in GA-FuL:
Problem: How to create new multivectors without mutation?
Solution: Composer classes
// 1. Composer for construction (Mutable during build)
var composer = processor.CreateMultivectorComposer();
composer.SetTerm(index1, scalar1);
composer.SetTerm(index2, scalar2);
composer.AddTerm(index3, scalar3);
// 2. Final multivector (Immutable)
var multivector = composer.GetMultivector();
// 3. Multivector itself is read-only
// multivector.Data is IReadOnlyDictionary<...>
Pattern:
Mutable Composer → Build → Immutable Data Structure
Classes:
XGaMultivectorComposer<T>: Creates multivectorsXGaKVectorComposer<T>: Creates k-vectorsXGaScalarComposer<T>: Creates scalars
Benefits:
- ✓ No unexpected side effects
- ✓ Thread-safe
- ✓ Easier to understand
- ✓ Optimization opportunities by compiler
DOP Principle 4: Separation of Data Representation and Schema
Rule: Store data schema separately from actual data
Implementation:
Through Interfaces and Abstract Classes:
// Schema: Interface defines the shape
interface IIndexSet {
int Count { get; }
bool Contains(int index);
IEnumerator<int> GetEnumerator();
}
// Representation: Different implementations
class EmptyIndexSet : IIndexSet { }
class SingleIndexSet : IIndexSet { }
class UInt64IndexSet : IIndexSet { }
class DenseIndexSet : IIndexSet { }
class SparseIndexSet : IIndexSet { }
Usage:
// Code uses only the schema (interface)
void ProcessBlade(IIndexSet indexSet) {
// Works with all implementations
foreach (var index in indexSet) {
// ...
}
}
// Automatic selection of the best implementation
var indexSet = IndexSetFactory.Create(indices);
// Returns EmptyIndexSet, SingleIndexSet, UInt64IndexSet, etc.
Additional Examples:
Multivectors:
// Schema
IReadOnlyDictionary<IIndexSet, T>
// Implementations
class EmptyDictionary<K, V> : IReadOnlyDictionary<K, V>
class SingleItemDictionary<K, V> : IReadOnlyDictionary<K, V>
class Dictionary<K, V> : IReadOnlyDictionary<K, V>
Benefits:
- ✓ Flexibility in implementation
- ✓ Runtime optimization
- ✓ Easy swapping of implementations
- ✓ Interface-based testing
Implementation of DOP Principles
Example: Index-Sets
Complete implementation of the four DOP principles:
DOP-4 (Schema):
// Interface as schema
interface IIndexSet {
int Count { get; }
bool Contains(int index);
IEnumerable<int> GetIndices();
}
DOP-2 (Generic Structures) + DOP-3 (Immutable):
// Implementation with read-only internal structures
class UInt64IndexSet : IIndexSet {
private readonly ulong _bitmap; // Immutable
public UInt64IndexSet(ulong bitmap) {
_bitmap = bitmap; // Set once, never changed
}
}
class SparseIndexSet : IIndexSet {
private readonly ImmutableHashSet<int> _indices; // Immutable
public SparseIndexSet(IEnumerable<int> indices) {
_indices = indices.ToImmutableHashSet();
}
}
DOP-1 (Separation of Behavior):
// Thin wrapper
class XGaBasisBlade {
public IIndexSet IndexSet { get; } // Just holds data
public XGaBasisBlade(IIndexSet indexSet) {
IndexSet = indexSet;
}
}
// Behavior in extension methods
static class XGaBasisBladeExtensions {
public static XGaBasisBlade Op(
this XGaBasisBlade blade1,
XGaBasisBlade blade2,
XGaMetric metric
) {
// Outer product logic here
}
}
Example: Multivectors
Complete DOP Implementation:
DOP-4 + DOP-2:
// Schema: Generic Dictionary
IReadOnlyDictionary<IIndexSet, T>
// Thin wrapper with schema
class XGaKVector<T> {
public IReadOnlyDictionary<IIndexSet, T> ScalarDictionary { get; }
public XGaProcessor<T> Processor { get; }
}
class XGaMultivector<T> {
public IReadOnlyDictionary<int, XGaKVector<T>> KVectorDictionary { get; }
public XGaProcessor<T> Processor { get; }
}
DOP-3 (Immutability via Composer):
// Mutable builder
class XGaMultivectorComposer<T> {
private Dictionary<IIndexSet, T> _terms = new();
public XGaMultivectorComposer<T> SetTerm(IIndexSet id, T scalar) {
_terms[id] = scalar;
return this;
}
public XGaMultivector<T> GetMultivector() {
// Select most efficient implementation
if (_terms.Count == 0)
return new ZeroMultivector<T>();
if (_terms.Count == 1)
return new SingleTermMultivector<T>(_terms.First());
return new SparseMultivector<T>(_terms.ToImmutableDictionary());
}
}
DOP-1 (Behavior separate):
// Extension methods for operations
static class XGaMultivectorExtensions {
public static XGaMultivector<T> Gp<T>(
this XGaMultivector<T> mv1,
XGaMultivector<T> mv2
) {
var composer = mv1.Processor.CreateMultivectorComposer();
// Geometric product logic
foreach (var (id1, scalar1) in mv1.Terms)
foreach (var (id2, scalar2) in mv2.Terms) {
var product = id1.Gp(id2, mv1.Processor.Metric);
composer.AddTerm(product.IndexSet,
mv1.Processor.ScalarProcessor.Times(
scalar1,
scalar2,
product.Sign
)
);
}
return composer.GetMultivector();
}
}
Practical Examples
Example 1: Adding a New Scalar Type
Thanks to DOP principles, this is simple:
// 1. Implement IScalarProcessor<T> (DOP-1: Behavior)
public class MyCustomScalarProcessor : INumericScalarProcessor<MyScalar> {
public MyScalar Add(MyScalar a, MyScalar b) => a + b;
public MyScalar Times(MyScalar a, MyScalar b) => a * b;
// ... additional operations
}
// 2. Use it with GA-FuL (DOP-4: Schema compatibility)
var scalarProcessor = new MyCustomScalarProcessor();
var processor = XGaProcessor<MyScalar>.CreateEuclidean(scalarProcessor);
// 3. All GA operations work automatically!
var v1 = processor.CreateComposer()
.SetVectorTerm(0, a)
.SetVectorTerm(1, b)
.SetVectorTerm(2, c)
.GetVector();
var v2 = processor.CreateComposer()
.SetVectorTerm(0, x)
.SetVectorTerm(1, y)
.SetVectorTerm(2, z)
.GetVector();
var result = v1.Gp(v2);
Example 2: New Index-Set Implementation
// 1. Implement IIndexSet (DOP-4: Schema)
public class BitVectorIndexSet : IIndexSet {
private readonly BitVector32 _bits; // DOP-3: Immutable
public int Count => _bits.Data.CountBits();
public bool Contains(int index) => _bits[1 << index];
// ...
}
// 2. Use it (DOP-2: Generic structure)
IIndexSet indexSet = new BitVectorIndexSet(bits);
var blade = new XGaBasisBlade(indexSet);
// 3. All operations work (DOP-1: Behavior separate)
var result = blade.Op(otherBlade, metric);
Example 3: New Geometric Operation
// DOP-1: Add behavior as extension method
public static class MyCustomOperations {
public static XGaMultivector<T> MyCustomProduct<T>(
this XGaMultivector<T> mv1,
XGaMultivector<T> mv2
) {
// DOP-2: Work with generic data structures
var composer = mv1.Processor.CreateMultivectorComposer();
foreach (var (id1, s1) in mv1.Terms)
foreach (var (id2, s2) in mv2.Terms) {
// Use existing structures
var id = ComputeResultingId(id1, id2);
var scalar = ComputeResultingScalar(s1, s2);
composer.AddTerm(id, scalar);
}
// DOP-3: Return immutable object
return composer.GetMultivector();
}
}
// Usage
var result = multivector1.MyCustomProduct(multivector2);
Summary
Core Design Intentions
| CDI | Goal | Benefit |
|---|---|---|
| CDI-1 | Scalar abstraction | Flexibility in scalar types |
| CDI-2 | Sparse storage | High-dimensional GAs practical |
| CDI-3 | Metaprogramming | Optimized code generation |
| CDI-4 | Layered design | Different abstraction levels |
| CDI-5 | Unified API | Consistent, extensible interface |
DOP Principles
| DOP | Principle | Implementation in GA-FuL |
|---|---|---|
| DOP-1 | Separation of behavior and data | Extension Methods + Thin Wrappers |
| DOP-2 | Generic data structures | Dictionary, Array, HashSet |
| DOP-3 | Immutable data | Composer pattern for construction |
| DOP-4 | Schema separation | Interfaces + Multiple implementations |
Benefits of the Combination
✓ Readable: Clear separation, simple structures ✓ Maintainable: Low coupling, high cohesion ✓ Extensible: New features easy to add ✓ Performant: Optimization possibilities at all levels ✓ Flexible: Supports different use cases ✓ Testable: Every component testable in isolation