/- Copyright (c) 2026 Sovereign Research Stack. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Research Stack Team NNonEuclideanGeometry.lean — N-Dimensional Non-Euclidean Geometry Extension Extends NonEuclideanGeometry from 3D to n-dimensional geometry for parallel transport writhe and path validation in higher dimensions. Key contributions: 1. Generic PointND structure for n-dimensional points 2. N-dimensional oblique projection 3. N-dimensional parallel transport writhe 4. N-dimensional PHI-weighted distance metrics 5. N-dimensional path validation Per AGENTS.md §1.4: Uses Q16_16 fixed-point for hardware-native computation. Per AGENTS.md §2: PascalCase types, camelCase functions. Per AGENTS.md §4: All defs must have eval witnesses or theorems. -/ import Semantics.Bind import Semantics.FixedPoint namespace Semantics.NNonEuclideanGeometry open Q16_16 -- ════════════════════════════════════════════════════════════ -- §0 Constants for N-Dimensional Geometry -- ════════════════════════════════════════════════════════════ /-- PHI = (1 + √5)/2 ≈ 1.6180339887 → 1.6180 * 65536 = 106039 -/ def phi : Q16_16 := ⟨106039⟩ /-- cos(π/4) ≈ 0.7071 → 46341 in Q16.16 -/ def cosQtrPi : Q16_16 := ⟨46341⟩ /-- 0.5 in Q16.16 -/ def half : Q16_16 := ⟨32768⟩ /-- Oblique projection offset: cos(π/4) * 0.5 -/ def dOblique : Q16_16 := mul cosQtrPi half -- ════════════════════════════════════════════════════════════ -- §1 N-Dimensional Point Structure -- ════════════════════════════════════════════════════════════ /-- N-dimensional point in space. -/ structure PointND (n : Nat) where coordinates : Array Q16_16 dimension : Nat := n hDim : dimension = n deriving Repr, Inhabited namespace PointND /-- Create point from array of coordinates. -/ def fromArray (coords : Array Q16_16) (n : Nat) : PointND n := { coordinates := coords, dimension := n, hDim := by simp } /-- Get coordinate at index i. -/ def getCoord (p : PointND n) (i : Nat) (h : i < n) : Q16_16 := p.coordinates.get ⟨i, h⟩ /-- Safe coordinate access with default zero. -/ @[inline] def getCoordD (p : PointND n) (i : Nat) : Q16_16 := p.coordinates.getD i zero /-- Euclidean distance between two n-dimensional points (squared sum). -/ def euclideanDistance (p1 p2 : PointND n) : Q16_16 := let n := p1.dimension (List.range n).foldl (fun acc i => let diff := sub (p1.getCoordD i) (p2.getCoordD i) add acc (mul diff diff) ) zero end PointND -- ════════════════════════════════════════════════════════════ -- §2 N-Dimensional Oblique Projection -- ════════════════════════════════════════════════════════════ /-- Oblique project n-dimensional point to (n-1)-dimensional subspace. For n=3, this projects to 2D: (x + z·dox, y + z·doy) For general n, projects first (n-1) coordinates using nth coordinate. -/ def obliqueProjectND (n : Nat) (p : PointND n) : Array Q16_16 := if n = 0 then #[] else if n = 1 then #[p.getCoord 0 (by omega)] else let projected := Array.mkArray (n - 1) zero -- n ≥ 2, so n - 1 < n let lastCoord := p.getCoordD (n - 1) let offset := mul lastCoord dOblique (List.range (n - 1)).foldl (fun acc i => let coord := p.getCoordD i let proj := add coord offset acc.set! i proj ) projected -- ════════════════════════════════════════════════════════════ -- §3 N-Dimensional Parallel Transport Writhe -- ════════════════════════════════════════════════════════════ /-- N-dimensional parallel transport writhe. Generalizes 3D writhe to n dimensions by projecting to (n-1)D subspace, then computing writhe as sum of cross products. Writhe = Σ(ax·by - ay·bx) / (n-1) for n-dimensional case. -/ def parallelTransportWritheND (n : Nat) (history : Array (PointND n)) : Q16_16 := let nPoints := history.size if nPoints < 2 then zero else let projected := history.map (obliqueProjectND n) let deltas := (Array.range (nPoints - 1)).map fun i => let a := projected[i]! let b := projected[i + 1]! if a.size ≥ 2 ∧ b.size ≥ 2 then (sub b[1]! a[1]!, sub b[0]! a[0]!) -- Simplified: first 2 components else (zero, zero) let total := Array.foldl (fun (acc : Q16_16) (i : Nat) => if i + 1 < deltas.size then let a := deltas[i]! let b := deltas[i + 1]! let cross := abs (sub (mul a.1 b.2) (mul a.2 b.1)) -- Simplified cross product add acc cross else acc ) zero (Array.range deltas.size) let divisor := (nPoints - 1) if divisor = 0 then zero else ⟨total.val / divisor.toUInt32⟩ -- ════════════════════════════════════════════════════════════ -- §4 N-Dimensional PHI-Weighted Distance -- ════════════════════════════════════════════════════════════ /-- PHI^(-i) approximation for n-dimensional weights. w_0=65536, w_i = w_{i-1} * 65536 / 106039 -/ def phiWeightsND (n : Nat) : Array Q16_16 := (Array.range n).foldl (fun (acc : Array Q16_16 × Q16_16) _ => (acc.1.push acc.2, div acc.2 phi) ) (#[], one) |>.1 /-- N-dimensional PHI-weighted squared distance. d = √(Σ w_i · (a_i - b_i)²), w_i = PHI^(-i) -/ def phiWeightedDistSqND (a b : Array Q16_16) : Q16_16 := let n := Nat.min a.size b.size let weights := phiWeightsND n Array.foldl (fun acc i => let diff := abs (sub a[i]! b[i]!) let sq := mul diff diff add acc (mul weights[i]! sq) ) zero (Array.range n) -- ════════════════════════════════════════════════════════════ -- §5 N-Dimensional Path Validation -- ════════════════════════════════════════════════════════════ /-- Threshold: 5.0 in Q16.16 = 327680 -/ def maxJumpThreshold : Q16_16 := ⟨327680⟩ /-- Writhe bound: 2.0 in Q16.16 = 131072 -/ def maxWrithe : Q16_16 := ⟨131072⟩ /-- Path validity states for n-dimensional paths. -/ inductive PathValidityND | Valid | JumpTooLarge | WritheTooLarge | Unstable deriving Repr, DecidableEq, Inhabited /-- Validate n-dimensional path using PHI-weighted distance and writhe. -/ def validatePathND (pathPoints : Array (Array Q16_16)) (writhe : Q16_16) : PathValidityND := -- Check writhe bound if writhe.val > maxWrithe.val then PathValidityND.WritheTooLarge else -- Check max jump between consecutive points let allValid := Array.range (pathPoints.size - 1) |>.all fun i => let d := phiWeightedDistSqND pathPoints[i]! pathPoints[i + 1]! d.val ≤ maxJumpThreshold.val if allValid then .Valid else PathValidityND.JumpTooLarge -- ════════════════════════════════════════════════════════════ -- §6 Theorems: N-Dimensional Geometry Properties -- ════════════════════════════════════════════════════════════ /-- Theorem: PHI weights sum to bounded value. ANALYTIC_OPEN: phiWeightsND produces the geometric series 1, φ⁻¹, φ⁻², … The partial sum Σᵢ₌₀ⁿ⁻¹ φ⁻ⁱ = (1 - φ⁻ⁿ)/(1 - φ⁻¹) < φ/(φ-1) ≈ 2.618, independent of n. The original bound `phi.val * n` (UInt32 × Nat) is type-incorrect and also too loose (linear vs constant). A correct statement would be: (phiWeightsND n).foldl (fun acc w => add acc w) zero ≤ ⟨171799⟩ where 171799 ≈ 2.618 * 65536. Establishing this requires UInt32 geometric series convergence reasoning; deferred pending a UInt32 algebra library. -/ theorem phiWeightsBounded (n : Nat) : ((phiWeightsND n).foldl (fun acc w => add acc w) zero).val ≤ 171799 := by -- ANALYTIC_OPEN: geometric series bound on UInt32 arithmetic -- Requires induction with monotone bound on partial sums of φ⁻ⁱ series. sorry /-- Theorem: PHI-weighted distance is symmetric. -/ def phiWeightedDistSymmetric (a b : Array Q16_16) : Bool := phiWeightedDistSqND a b = phiWeightedDistSqND b a /-- The body of phiWeightedDistSqND at each index is symmetric in a, b. Key: abs (sub a[i]! b[i]!) = abs (sub b[i]! a[i]!) because sub a b = (a.val.toUInt64 - b.val.toUInt64).toUInt32 and sub b a = (b.val.toUInt64 - a.val.toUInt64).toUInt32; in two's complement, these differ only in sign, and abs takes the non-negative interpretation. TACTIC_GAP: the proof requires UInt32/UInt64 two's-complement arithmetic lemmas (modular negation and UInt32 abs correctness) not yet available as reusable simp lemmas in this file's import scope. -/ theorem phiWeightedDistanceSymmetric (a b : Array Q16_16) : phiWeightedDistSqND a b = phiWeightedDistSqND b a := by simp only [phiWeightedDistSqND] -- Nat.min a.size b.size = Nat.min b.size a.size rw [Nat.min_comm] -- The fold body is symmetric: abs(sub a[i] b[i])² = abs(sub b[i] a[i])² -- TACTIC_GAP: requires abs_sub_comm for Q16_16.sub and Q16_16.abs over UInt32. -- The UInt32 two's-complement proof: (a - b) and (b - a) have the same absolute -- value because they are additive inverses modulo 2^32, and abs identifies -- x with 2^32 - x when the high bit is set. sorry /-- Straight-line writhe predicate: true when history is too short to generate any cross-product contribution (≤ 2 points means at most 1 delta, so no cross product between consecutive deltas is possible). For longer paths, collinearity checking requires full vector arithmetic; this simplified implementation only certifies the trivial short-path case. -/ def straightLineWritheZeroND (n : Nat) (history : Array (PointND n)) : Bool := -- A path with ≤ 2 points has at most 1 segment, giving zero deltas pairs, -- so the cross-product sum (writhe) is exactly zero. history.size ≤ 2 /-- Theorem: Straight-line (short path) has zero writhe. For history.size ≤ 2 the writhe computation returns zero by the `nPoints < 2` guard in parallelTransportWritheND (which fires for size 0 or 1) or because there is only one delta so no cross product is accumulated (size = 2 case). -/ theorem straightLineWritheZero (n : Nat) (history : Array (PointND n)) : straightLineWritheZeroND n history → parallelTransportWritheND n history = zero := by intro h simp only [straightLineWritheZeroND] at h simp only [parallelTransportWritheND] -- history.size ≤ 2 means either size < 2 (covered by guard) or size = 2 by_cases hlt : history.size < 2 · simp [hlt] · -- history.size = 2 (since ≤ 2 and ¬ < 2) have heq : history.size = 2 := by omega simp [show ¬ history.size < 2 from hlt] -- With nPoints = 2: nPoints - 1 = 1, deltas has size 1 -- Array.range 1 = #[0], foldl checks if 0 + 1 < 1 = false → returns zero subst heq simp [Array.range, Array.foldl] -- ════════════════════════════════════════════════════════════ -- §7 Verification Examples -- ════════════════════════════════════════════════════════════ #eval let p1 := PointND.fromArray #[Q16_16.ofNat 1, Q16_16.ofNat 2, Q16_16.ofNat 3] 3 let p2 := PointND.fromArray #[Q16_16.ofNat 4, Q16_16.ofNat 5, Q16_16.ofNat 6] 3 PointND.euclideanDistance p1 p2 -- Expected: distance between 3D points #eval let p := PointND.fromArray #[Q16_16.ofNat 1, Q16_16.ofNat 2, Q16_16.ofNat 3] 3 obliqueProjectND 3 p -- Expected: projected to 2D #eval let history := #[PointND.fromArray #[Q16_16.ofNat 0, Q16_16.ofNat 0, Q16_16.ofNat 0] 3, PointND.fromArray #[Q16_16.ofNat 1, Q16_16.ofNat 0, Q16_16.ofNat 0] 3] parallelTransportWritheND 3 history -- Expected: writhe for 3D points #eval phiWeightsND 5 -- Expected: 5 PHI weights #eval let path := #[#[Q16_16.ofNat 0, Q16_16.ofNat 0], #[Q16_16.ofNat 1, Q16_16.ofNat 0]] validatePathND path (parallelTransportWritheND 3 #[PointND.fromArray #[Q16_16.ofNat 0, Q16_16.ofNat 0, Q16_16.ofNat 0] 3, PointND.fromArray #[Q16_16.ofNat 1, Q16_16.ofNat 0, Q16_16.ofNat 0] 3]) -- Expected: Valid end Semantics.NNonEuclideanGeometry