Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .agents/onboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ learn how geonum implements the dual in src/angle.rs:473~490

learn how angle impls PartialEq and Eq in src/angle.rs:572~590

learn how angle overloads arithmetic operators in src/angle.rs:592~740
learn how angle overloads arithmetic operators in src/angle.rs:592~805

learn how to construct geonum with new, new_with_angle from src/geonum_mod.rs:32~49

Expand All @@ -81,6 +81,6 @@ learn about angle forward only geometry from the it_sets_angle_forward_geometry_

read only tests/angle_arithmetic_test.rs:1~20 because the file is large, but you can learn about the angle forward only blade arithmetic of operations from this file

read the its_a_limit:40-119, it_proves_differentiation_cycles_grades:764-915 tests in tests/calculus_test.rs to understand how geonum automates calculus
read the it_shows_limits_discard_what_angles_preserve:350-387, it_proves_differentiation_cycles_grades:586-664 tests in tests/calculus_test.rs to understand how geonum automates calculus

tests are styled as trojan horses for simplicity. conventional jargon promising symbol salad but readers get simple arithmetic in test contents. example tests: it_handles_conformal_split:4694-4805, it_handles_inversive_distance:4807-4937 in tests/cga_test.rs
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# changelog

## 0.12.0 (2026-03-31)

### fixed
- **BREAKING**: pow() now scales the total angle by n instead of adding nπ — matches repeated multiplication for all n

### added
- Mul<f64> for Angle and &Angle: scalar multiplication of angles
- calculus_test.rs: power rule as angle readout, factorials from angle descent, limits as lossy projections, fundamental theorem as geometric interference, gradient, laplacian, line/surface/volume integrals
- taylor_series_test.rs: taylor coefficients as geometric normalizations, e^x as uniform angle contribution, sin/cos as grade-filtered projections, euler's formula as grade decomposition, convergence as angle descent dominance
- algebra_test.rs: fundamental theorem of algebra via winding numbers — degree = wraps, roots = unwindings, polynomial evaluation on circles, roots of unity as generalized Q lattice

### changed
- replaced old calculus_test.rs (24 tests) with power-rule-anchored suite (23 tests)

## 0.11.0 (2026-03-20)

### breaking
Expand All @@ -9,6 +23,9 @@
- `normalize_boundaries()` removed — boundary logic is algebraic in the tangent sum formula
- `Display` for Angle now shows `t` instead of `rem`

### fixed
- pow() now scales the total angle by n instead of adding nπ — matches repeated multiplication for all n

### added

- `Angle::t()` — projection ratio between adjacent π/2 blades
Expand All @@ -19,6 +36,10 @@
- `Angle::near_rem(radians)` — remainder comparison within tolerance
- `Geonum::near(&other)` — magnitude + angle comparison within tolerance
- `Geonum::near_mag(value)` — magnitude comparison within tolerance
- Mul<f64> for Angle and &Angle: scalar multiplication of angles
- calculus_test.rs: power rule as angle readout, factorials from angle descent, limits as lossy projections, fundamental theorem as geometric interference, gradient, laplacian, line/surface/volume integrals
- taylor_series_test.rs: taylor coefficients as geometric normalizations, e^x as uniform angle contribution, sin/cos as grade-filtered projections, euler's formula as grade decomposition, convergence as angle descent dominance
- algebra_test.rs: fundamental theorem of algebra via winding numbers — degree = wraps, roots = unwindings, polynomial evaluation on circles, roots of unity as generalized Q lattice

### changed

Expand All @@ -32,6 +53,8 @@
- `Geonum::dot()`, `wedge()`, `cos()`, `sin()`, `distance_to()`, `project_to_angle()` use `cos_sin()`
- `Geonum::geo()` computes single `cos_sin()` for both dot and wedge
- `Geonum` addition uses rational projection pipeline: cos_sin (0 sqrts) → sum → magnitude (1 sqrt) → cartesian recovery (0 sqrts)
- replaced old calculus_test.rs (24 tests) with power-rule-anchored suite (23 tests)
- updated angle_arithmetic_test, numbers_test, geonum_mod unit test pow expectations to match corrected angle scaling

### performance

Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "geonum"
version = "0.11.0"
version = "0.12.0"
edition = "2021"
repository = "https://github.com/mxfactorial/geonum"
description = "geometric number library supporting unlimited dimensions with O(1) complexity"
Expand Down
24 changes: 13 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ see [tests](https://github.com/mxfactorial/geonum/tree/main/tests) to learn how
❯ ls -1 tests
addition_test.rs
affine_test.rs
algebra_test.rs
algorithms_test.rs
angle_arithmetic_test.rs
arithmetic_test.rs
Expand Down Expand Up @@ -170,6 +171,7 @@ qm_test.rs
rendering_test.rs
robotics_test.rs
set_theory_test.rs
taylor_series_test.rs
tensor_test.rs
trigonometry_test.rs
```
Expand Down Expand Up @@ -402,17 +404,17 @@ geometric numbers build dimensions by rotating—not stacking
- it_proves_rotational_quadrature_expresses_quadratic_forms:1419-1593

- tests/calculus_test.rs
- its_a_limit:40-119
- its_a_derivative:121-165
- its_an_integral:167-218
- its_a_gradient:310-358
- its_a_divergence:360-409
- its_a_curl:411-499
- its_a_laplacian:501-605
- its_a_line_integral:607-633
- its_a_surface_integral:635-662
- it_proves_differentiation_cycles_grades:764-915
- it_proves_fundamental_theorem_is_accumulation_equals_interference:1002-1053
- it_encodes_the_power_in_the_angle:35-88
- it_derives_x_squared_without_limits:91-121
- it_shows_limits_discard_what_angles_preserve:350-387
- it_shows_limits_lose_the_tangent_normal_dual:390-427
- it_shows_factorial_emerges_from_angle_descent:501-542
- it_proves_differentiation_cycles_grades:586-664
- it_proves_fundamental_theorem_is_accumulation_equals_interference:704-743
- its_a_gradient:806-853
- its_a_laplacian:856-902
- its_a_line_integral:909-931
- its_a_surface_integral:934-948

- tests/mechanics_test.rs
- it_changes_kinematic_level_by_cycling_grade:46-195
Expand Down
61 changes: 44 additions & 17 deletions src/angle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -685,39 +685,66 @@ impl Mul<&Angle> for &Angle {
}
}

impl Div<f64> for Angle {
impl Mul<f64> for Angle {
type Output = Angle;

fn div(self, divisor: f64) -> Angle {
// round-trip through radians — no closed-form t n-section
let total_radians = (self.blade as f64) * (PI / 2.0) + 2.0 * self.t.atan();
let divided = total_radians / divisor;
// convert back: blade from quarter turns, t from remainder
fn mul(self, scalar: f64) -> Angle {
// scale blade count and remainder separately
// avoids converting large blade to radians (blade * π/2 → huge float)
let scaled_blade = self.blade as f64 * scalar;
let blade_whole = scaled_blade.floor();
let blade_frac_rem = (scaled_blade - blade_whole) * (PI / 2.0);

let scaled_rem = self.rem() * scalar + blade_frac_rem;

// remainder overflow adjusts blade
let quarter_pi = PI / 2.0;
let normalized = if divided < 0.0 {
let full = (divided.abs() / (4.0 * quarter_pi)).ceil();
divided + full * 4.0 * quarter_pi
let extra_blades = (scaled_rem / quarter_pi).floor();
let final_rem = scaled_rem - extra_blades * quarter_pi;

let total_blade = blade_whole + extra_blades;
let normalized_blade = if total_blade < 0.0 {
let full = ((-total_blade + 3.0) / 4.0).ceil() * 4.0;
(total_blade + full) as usize
} else {
divided
total_blade as usize
};
let blade = (normalized / quarter_pi) as usize;
let rem = normalized % quarter_pi;
if rem.abs() < 1e-10 {
Angle { blade, t: 0.0 }

if final_rem.abs() < 1e-10 {
Angle {
blade: normalized_blade,
t: 0.0,
}
} else {
Angle {
blade,
t: (rem / 2.0).tan(),
blade: normalized_blade,
t: (final_rem / 2.0).tan(),
}
}
}
}

impl Mul<f64> for &Angle {
type Output = Angle;

fn mul(self, scalar: f64) -> Angle {
(*self) * scalar
}
}

impl Div<f64> for Angle {
type Output = Angle;

fn div(self, divisor: f64) -> Angle {
self * (1.0 / divisor)
}
}

impl Div<f64> for &Angle {
type Output = Angle;

fn div(self, divisor: f64) -> Angle {
(*self) / divisor
*self * (1.0 / divisor)
}
}

Expand Down
21 changes: 10 additions & 11 deletions src/geonum_mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -535,9 +535,10 @@ impl Geonum {
/// # returns
/// a new geometric number representing self^n
pub fn pow(self, n: f64) -> Self {
// x^n = [mag^n, n*angle]
Self {
mag: self.mag.powf(n),
angle: self.angle * Angle::new(n, 1.0),
angle: self.angle * n,
}
}

Expand Down Expand Up @@ -1691,25 +1692,23 @@ mod tests {
fn it_computes_powers() {
let g = Geonum::new(2.0, 1.0, 4.0); // [2, PI/4] blade=0, value=PI/4

// pow scales total angle by n: [mag^n, n*angle]
// matches repeated multiplication: g * g adds angles π/4 + π/4 = π/2 → blade=1
let squared = g.pow(2.0);
assert_eq!(squared.mag, 4.0); // 2^2 = 4
// pow(2.0) adds Angle::new(2.0, 1.0) which is 2*PI radians = 4 quarter-turns
// original blade=0, added blade=4, final blade=4
assert_eq!(squared.angle.blade(), 4);
assert!((squared.angle.rem() - PI / 4.0).abs() < EPSILON);
assert_eq!(squared.angle.blade(), 1); // 2 * π/4 = π/2 = 1 blade
assert!(squared.angle.rem().abs() < EPSILON); // exactly on boundary

// pow(1.0) scales angle by 1: identity
let identity = g.pow(1.0);
assert!((identity.mag - g.mag).abs() < EPSILON);
// pow(1.0) adds Angle::new(1.0, 1.0) which is PI radians = 2 quarter-turns
// original blade=0, added blade=2, final blade=2
assert_eq!(identity.angle.blade(), 2);
assert_eq!(identity.angle.blade(), g.angle.blade());
assert!((identity.angle.rem() - g.angle.rem()).abs() < EPSILON);

// pow(3.0) scales angle by 3: 3 * π/4 = 3π/4 → blade=1, rem=π/4
let cubed = g.pow(3.0);
assert_eq!(cubed.mag, 8.0); // 2^3 = 8
// pow(3.0) adds Angle::new(3.0, 1.0) which is 3*PI radians = 6 quarter-turns
// original blade=0, added blade=6, final blade=6
assert_eq!(cubed.angle.blade(), 6);
assert_eq!(cubed.angle.blade(), 1); // 3π/4 = 1 blade + π/4
assert!((cubed.angle.rem() - PI / 4.0).abs() < EPSILON);
}

Expand Down
Loading