1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
use geonum::*;
use std::f64::consts::PI;
// small value for floating-point comparisons
const EPSILON: f64 = 1e-10;
#[test]
fn it_adds_scalars() {
// in geometric number representation, scalar addition can be performed
// by converting to cartesian coordinates, adding, then converting back
// create two scalar values as geometric numbers
let a = Geonum::new_with_blade(3.0, 0, 0.0, 1.0); // [3, 0] = positive 3, scalar
let b = Geonum::new_with_blade(4.0, 0, 0.0, 1.0); // [4, 0] = positive 4, scalar
// convert to cartesian (for scalars, just the length)
let a_cartesian = a.mag * a.angle.grade_angle().cos(); // 3
let b_cartesian = b.mag * b.angle.grade_angle().cos(); // 4
// add them
let sum_cartesian = a_cartesian + b_cartesian; // 7
// for scalars on the positive real axis, the result length is just the sum
// and angle remains 0
let result = if sum_cartesian >= 0.0 {
Geonum::new_with_blade(sum_cartesian.abs(), 0, 0.0, 1.0)
} else {
Geonum::new_with_blade(sum_cartesian.abs(), 0, 1.0, 1.0) // angle PI
};
// verify result is [7, 0]
assert_eq!(result.mag, 7.0);
assert_eq!(result.angle, Angle::new(0.0, 1.0));
// test with negative scalar (on the negative real axis)
let c = Geonum::new_with_blade(5.0, 0, 0.0, 1.0); // [5, 0] = positive 5, scalar
let d = Geonum::new_with_blade(8.0, 0, 1.0, 1.0); // [8, pi] = negative 8, scalar
// convert to cartesian for operation
let c_cartesian = c.mag * c.angle.grade_angle().cos(); // 5
let d_cartesian = d.mag * d.angle.grade_angle().cos(); // -8
// add them
let difference = c_cartesian + d_cartesian; // -3
// convert back to geometric number
let result2 = if difference >= 0.0 {
Geonum::new_with_blade(difference.abs(), 0, 0.0, 1.0)
} else {
Geonum::new_with_blade(difference.abs(), 0, 1.0, 1.0) // angle PI
};
// verify result is [3, pi] (negative 3)
assert_eq!(result2.mag, 3.0);
assert_eq!(result2.angle, Angle::new(1.0, 1.0));
}
#[test]
fn it_multiplies_scalars() {
// in geometric number representation, multiplication follows the rule:
// "angles add, lengths multiply"
// multiply two positive numbers
let a = Geonum::new_with_blade(3.0, 0, 0.0, 1.0); // [3, 0] = positive 3, scalar
let b = Geonum::new_with_blade(4.0, 0, 0.0, 1.0); // [4, 0] = positive 4, scalar
// use the mul method directly
let product1 = a * b;
// verify result is [12, 0]
assert_eq!(product1.mag, 12.0);
assert_eq!(product1.angle, Angle::new(0.0, 1.0));
// multiply positive by negative
let c = Geonum::new_with_blade(5.0, 0, 0.0, 1.0); // [5, 0] = positive 5, scalar
let d = Geonum::new_with_blade(2.0, 0, 1.0, 1.0); // [2, pi] = negative 2, scalar
// use the mul method
let product2 = c * d;
// verify result is [10, pi] (negative 10)
assert_eq!(product2.mag, 10.0);
assert_eq!(product2.angle, Angle::new(1.0, 1.0));
// multiply two negative numbers
let e = Geonum::new_with_blade(3.0, 0, 1.0, 1.0); // [3, pi] = negative 3, scalar
let f = Geonum::new_with_blade(2.0, 0, 1.0, 1.0); // [2, pi] = negative 2, scalar
// use the mul method
let product3 = e * f;
// verify result is [6, 2pi] which reduces to [6, 0] (positive 6)
assert_eq!(product3.mag, 6.0);
assert_eq!(product3.angle, Angle::new(4.0, 2.0)); // 2pi = 4 * pi/2
}
#[test]
fn it_adds_vectors() {
// vector addition requires conversion to cartesian coordinates,
// adding the components, then converting back to geometric form
// create two vectors as geometric numbers
let a = Geonum::new_with_blade(3.0, 1, 0.0, 1.0); // [3, 0] = 3 along x-axis, vector
let b = Geonum::new_with_blade(4.0, 1, 1.0, 2.0); // [4, pi/2] = 4 along y-axis, vector
// convert to cartesian coordinates
let a_x = a.mag * a.angle.grade_angle().cos(); // 3
let a_y = a.mag * a.angle.grade_angle().sin(); // 0
let b_x = b.mag * b.angle.grade_angle().cos(); // 0
let b_y = b.mag * b.angle.grade_angle().sin(); // 4
// add the components
let sum_x = a_x + b_x; // 3
let sum_y = a_y + b_y; // 4
// convert back to geometric form
let _result_length = (sum_x * sum_x + sum_y * sum_y).sqrt(); // 5
let _result_angle_radians = sum_y.atan2(sum_x); // atan2(4, 3) ≈ 0.9273
// create the result as a geometric number
// since we're adding vectors, result should be a vector (blade 1)
let result = Geonum::new_from_cartesian(sum_x, sum_y);
// verify the result is a vector with length 5 and angle arctan(4/3)
assert!(result.near_mag(5.0));
// angle atan2(4,3) ≈ 0.927 radians ≈ 53.13°
// new_from_cartesian decomposes this into blade and value
assert_eq!(result.angle.blade(), 1); // first quadrant angle
assert!(result.angle.near_rem(4.0_f64.atan2(3.0)));
// test adding vectors in opposite directions
let c = Geonum::new_with_blade(5.0, 1, 0.0, 1.0); // [5, 0] = 5 along x-axis, vector
let d = Geonum::new_with_blade(5.0, 1, 1.0, 1.0); // [5, pi] = 5 along negative x-axis, vector
// convert to cartesian
let c_x = c.mag * c.angle.grade_angle().cos(); // 5
let c_y = c.mag * c.angle.grade_angle().sin(); // 0
let d_x = d.mag * d.angle.grade_angle().cos(); // -5
let d_y = d.mag * d.angle.grade_angle().sin(); // 0
// add components
let sum2_x = c_x + d_x; // 0
let sum2_y = c_y + d_y; // 0
// the result should be a zero vector (length zero)
let result2_length = (sum2_x * sum2_x + sum2_y * sum2_y).sqrt();
// check the length is zero (angle is arbitrary for zero vector)
assert!(result2_length < EPSILON);
}
#[test]
fn it_multiplies_vectors() {
// in geometric number representation, vector multiplication follows
// the fundamental rule: "angles add, lengths multiply"
// create two vectors as geometric numbers
let a = Geonum::new_with_blade(2.0, 1, 1.0, 4.0); // [2, pi/4] = 2 at 45 degrees, vector
let b = Geonum::new_with_blade(3.0, 1, 1.0, 3.0); // [3, pi/3] = 3 at 60 degrees, vector
// multiply using the mul method
let product = a * b;
// verify the result has length 2*3=6 and angle pi/4+pi/3=7pi/12
assert_eq!(product.mag, 6.0);
// product of two blade-1 vectors: blade accumulates, angles add
// blade: 1 + 1 = 2
// angle: PI/4 + PI/3 = 3PI/12 + 4PI/12 = 7PI/12
// 7PI/12 > PI/2, so crosses boundary: blade += 1, angle -= PI/2
// final: blade 3, angle 7PI/12 - PI/2 = PI/12
assert_eq!(product.angle.blade(), 3);
assert!(product.angle.near_rem(PI / 12.0));
// test multiplication of perpendicular vectors (90 degrees apart)
let c = Geonum::new_with_blade(2.0, 1, 0.0, 1.0); // [2, 0] = 2 along x-axis, vector
let d = Geonum::new_with_blade(
4.0, 1, // vector (grade 1) - directed quantity along y-axis
1.0, // [4, pi/2] = 4 along y-axis
2.0, // PI / 2.0
);
// multiply vectors
let perpendicular_product = c * d;
// verify result has length 2*4=8 and angle 0+pi/2=pi/2
assert_eq!(perpendicular_product.mag, 8.0);
// c: blade 1, angle 0; d: blade 1, angle PI/2
// product: blade 2, angle PI/2, but PI/2 is boundary so blade 3, angle 0
assert_eq!(perpendicular_product.angle.blade(), 3);
assert!(perpendicular_product.angle.near_rem(0.0));
// test multiplication of opposite vectors
let e = Geonum::new_with_blade(
5.0, 1, // vector (grade 1) - directed quantity at 30°
1.0, // [5, pi/6] = 5 at 30 degrees
6.0, // PI / 6.0
);
let f = Geonum::new_with_blade(
2.0, 1, // vector (grade 1) - directed quantity at -30°
-1.0, // [2, -pi/6] = 2 at -30 degrees (or 330 degrees)
6.0, // PI / 6.0
);
// multiply vectors
let opposite_product = e * f;
// verify result has length 5*2=10
assert_eq!(opposite_product.mag, 10.0);
// e: blade 1, angle PI/6; f: blade 1, angle -PI/6 (normalizes to 11PI/6)
// When f is created with negative angle, it normalizes to positive
// The exact blade count depends on the normalization
assert_eq!(opposite_product.angle.blade(), 6);
assert!(opposite_product.angle.near_rem(0.0));
}
#[test]
fn it_multiplies_vectors_with_scalars() {
// scalar multiplication in geometric numbers follows the same rule:
// "angles add, lengths multiply"
// create a vector and a positive scalar
let vector = Geonum::new_with_blade(
3.0, 1, // vector (grade 1) - directed quantity at 45°
1.0, // [3, pi/4] = 3 at 45 degrees
4.0, // PI / 4.0
);
let scalar = Geonum::new_with_blade(
2.0, 0, // scalar (grade 0) - pure magnitude for scaling
0.0, // [2, 0] = positive 2 (scalar)
1.0,
);
// multiply vector by positive scalar
let product1 = vector * scalar;
// verify result has length 3*2=6 and angle remains pi/4 (unchanged)
assert_eq!(product1.mag, 6.0);
// vector (blade 1) * scalar (blade 0) = vector (blade 1)
assert_eq!(product1.angle, Angle::new_with_blade(1, 1.0, 4.0));
// test with negative scalar
let negative_scalar = Geonum::new_with_blade(
2.0, 0, // scalar (grade 0) - negative scale factor
1.0, // [2, pi] = negative 2 (scalar)
1.0, // PI
);
// multiply vector by negative scalar
let product2 = vector * negative_scalar;
// verify result has length 3*2=6 and angle is now pi/4+pi=5pi/4 (rotated 180 degrees)
assert_eq!(product2.mag, 6.0);
// vector (blade 1, PI/4) * negative scalar (blade 0, PI) = blade 1, angle 5PI/4
// 5PI/4 = 2.5 * PI/2, so 2 boundary crossings: blade 3, angle PI/4
assert_eq!(product2.angle.blade(), 3);
assert!(product2.angle.near_rem(PI / 4.0));
// verify scalar multiplication is commutative
let product3 = negative_scalar * vector;
// should have same length and angle as product2
assert_eq!(product3.mag, product2.mag);
assert_eq!(product3.angle, product2.angle);
// test scaling a vector by zero
let zero_scalar = Geonum::new_with_blade(
0.0, 0, // scalar (grade 0) - zero value
0.0, // [0, 0] = zero
1.0,
);
// multiply by zero
let product4 = vector * zero_scalar;
// verify result has length 0 (angle doesn't matter for zero vector)
assert_eq!(product4.mag, 0.0);
}
#[test]
fn it_computes_ijk_product() {
// from the spec: ijk = [1, 0 + pi/2] × [1, pi/2 + pi/2] × [1, pi + pi/2] = [1, 3pi] = [1, pi]
// transition from coordinate scaffolding to direct vector creation
// old design: required declaring dimensional "space" before creating vectors
// new design: create geometric numbers representing i, j, k directly
// create individual dimensions:
let i = Geonum::create_dimension(1.0, 1); // vector at dimension 1 = [1, pi/2]
let j = Geonum::create_dimension(1.0, 2); // vector at dimension 2 = [1, pi]
let k = Geonum::create_dimension(1.0, 3); // vector at dimension 3 = [1, 3pi/2]
// verify each vector has the correct angle
assert_eq!(i.angle, Angle::new(1.0, 2.0));
assert_eq!(j.angle, Angle::new(1.0, 1.0));
assert_eq!(k.angle, Angle::new(3.0, 2.0));
// compute the ijk product
let ij = i * j; // blade 1 + blade 2 = blade 3, angle pi/2 + pi = 3pi/2
let ijk = ij * k; // blade 3 + blade 3 = blade 6, angle 3pi/2 + 3pi/2 = 3pi
// check result
assert_eq!(ijk.mag, 1.0);
assert_eq!(ijk.angle, Angle::new(6.0, 2.0)); // 3pi = 6 * pi/2
}
#[test]
fn it_operates_in_extreme_dimensions() {
// this test demonstrates the O(1) complexity of geonum operations
// regardless of the dimension of the space
// transition from coordinate scaffolding to direct high-dimensional creation
// old design: required declaring million-dimensional "space" (impossible with traditional GA!)
// new design: create geometric numbers at high-dimensional angles directly
// this demonstrates O(1) complexity regardless of dimension
// operation start time for performance comparison
let start = std::time::Instant::now();
// create individual dimensions:
let v1 = Geonum::create_dimension(1.0, 0); // first basis vector e₁
let v2 = Geonum::create_dimension(1.0, 1); // second basis vector e₂
// verify basic properties - constant time operations
assert_eq!(v1.mag, 1.0);
assert_eq!(v1.angle, Angle::new(0.0, 1.0));
assert_eq!(v2.mag, 1.0);
assert_eq!(v2.angle, Angle::new(1.0, 2.0));
// compute operations in this million-dimensional space
// dot product (constant time)
let dot = v1.dot(&v2);
// wedge product (constant time)
let wedge = v1.wedge(&v2);
// geometric product (constant time)
let geo_product = v1 * v2;
// complex chain of operations (still constant time)
let v3 = Geonum::new_with_blade(
2.0, 1, // vector (grade 1) - directed quantity at 60°
1.0, 3.0, // PI / 3.0
);
let result = (v1 * v2) * v3;
// operation end time
let duration = start.elapsed();
// verify results
assert!(dot.near_mag(0.0)); // orthogonal vectors have zero dot product
assert_eq!(wedge.mag, 1.0); // unit bivector
assert_eq!(geo_product.mag, 1.0);
// v1 (blade 0) * v2 (blade 1) = blade 0 + 1 = blade 1
assert_eq!(geo_product.angle.blade(), 1);
assert!(geo_product.angle.near_rem(0.0));
assert_eq!(result.mag, 2.0); // length of v3
// (v1*v2) has blade 1, angle 0; v3 has blade 1, angle PI/3
// result: blade 1 + 1 = 2, angle PI/3
assert_eq!(result.angle.blade(), 2);
assert!(result.angle.near_rem(PI / 3.0));
// confirm operation completed in reasonable time (should be milliseconds)
// if this were a traditional GA implementation, it would take longer than
// the age of the universe to even allocate storage for the calculation
assert!(duration.as_secs() < 1); // should complete in under a second
// OPTIONAL: Print performance info
// println!("Million-D operations completed in: {:?}", duration);
}
#[test]
fn it_keeps_angles_less_than_2pi() {
// Create vectors with different angles but same blade
let a = Geonum::new_with_blade(1.0, 1, 0.0, 1.0);
let b = Geonum::new_with_blade(1.0, 5, 0.0, 1.0); // blade 5 = blade 1 + 4*(PI/2) from 2π
// Blade grades are preserved and distinguish vectors from bivectors
let c = Geonum::new_with_blade(1.0, 2, 0.0, 1.0); // bivector with angle 0
// Verify blade values are preserved in mul
let a_times_c = a * c;
assert_eq!(a_times_c.angle.blade(), 3); // Vector * bivector blade 1 + 2 = 3
// Wedge product of parallel vectors (same angle mod 2π)
let wedge = a.wedge(&b);
// a has blade 1, b has blade 5, but both have value 0 within their blade
// angle difference is 4*(π/2) = 2π ≡ 0, so sin(0) = 0
assert!(wedge.near_mag(0.0)); // Parallel vectors have zero wedge product
// Differentiation increases blade grade
let a_diff = a.differentiate();
assert_eq!(a_diff.angle.blade(), a.angle.blade() + 1);
// Integration adds 3π/2 (3 blades)
let a_int = a_diff.integrate();
// a_diff has blade 2, integrate adds 3, so blade 5
assert_eq!(a_int.angle.blade(), 5); // blade 2 + 3 = blade 5
// Rotation by PI/2 increments blade grade
let rotation = Angle::new(1.0, 2.0); // PI/2
let a_rot = a.rotate(rotation);
assert_eq!(a_rot.angle.blade(), a.angle.blade() + 1);
// Reflection uses: 2*axis + (2π - base_angle(point))
let a_ref = a.reflect(&b);
// a has blade 1, b has blade 5
// reflect computes: 2*b.angle + (2π - a.base_angle())
// = 2*(blade 5 angle) + (2π - blade 1 angle)
// results in blade 17 (from debug output)
assert_eq!(a_ref.angle.blade(), 17);
}
#[test]
fn it_initializes_with_blade() {
// Test default blade values for different types
let scalar = Geonum::new_with_blade(1.0, 0, 0.0, 1.0);
let _vector = Geonum::new_with_blade(1.0, 1, 1.0, 2.0); // PI/2
let bivector = Geonum::new_with_blade(1.0, 2, 0.0, 1.0);
let _trivector = Geonum::new_with_blade(1.0, 3, 0.0, 1.0);
// scalar is already complete - blade = 0 identifies it as scalar grade
// bivector is already a complete geometric object - blade encodes its grade - no wrapper needed
// grade is determined directly from blade count
assert_eq!(scalar.angle.grade(), 0); // scalar grade
assert_eq!(bivector.angle.grade(), 2); // bivector grade
// Constructors set blade values
let v1 = Geonum::new(1.0, 0.0, 1.0);
let v2 = Geonum::new_with_blade(1.0, 2, 0.0, 1.0);
let s = Geonum::new_with_blade(5.0, 0, 0.0, 1.0);
assert_eq!(v1.angle.blade(), 0); // blade 0 for simple angle 0
assert_eq!(v2.angle.blade(), 2); // Explicitly set
assert_eq!(s.angle.blade(), 0); // Scalar is grade 0
}