mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
233 lines
12 KiB
Text
233 lines
12 KiB
Text
/- 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
|
||
|
||
HyperbolicEncoding.lean — Hyperbolic Manifold Coordinate Encoding
|
||
|
||
Replaces infra/hyperbolic_encoding.py with a formal Lean module.
|
||
Defines Poincaré disk coordinates and Möbius transformations for semantic vector encoding.
|
||
|
||
Per AGENTS.md:
|
||
- Q16_16 for scoring (§1.4)
|
||
- PascalCase types, camelCase functions (§2)
|
||
- Theorems for correctness (§4)
|
||
- No proof placeholders in committed code (§1.6)
|
||
-/
|
||
|
||
import Mathlib.Data.Nat.Basic
|
||
import Mathlib.Data.List.Basic
|
||
import Std
|
||
|
||
namespace Semantics.HyperbolicEncoding
|
||
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
-- §0 Q16_16 Fixed-Point Arithmetic
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
|
||
structure Q16_16 where
|
||
raw : Int
|
||
deriving Repr, DecidableEq, Inhabited
|
||
|
||
namespace Q16_16
|
||
def zero : Q16_16 := ⟨0⟩
|
||
def one : Q16_16 := ⟨65536⟩
|
||
def ofFrac (num denom : Nat) : Q16_16 :=
|
||
if denom = 0 then zero else ⟨(num * 65536) / denom⟩
|
||
def toNat (q : Q16_16) : Nat := q.raw.toNat
|
||
end Q16_16
|
||
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
-- §1 Hyperbolic Vector Structure
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
|
||
structure HyperbolicVector where
|
||
x : Q16_16 -- x coordinate in Poincaré disk
|
||
y : Q16_16 -- y coordinate in Poincaré disk
|
||
dimension : Nat -- Original dimension
|
||
norm : Q16_16 -- Distance from origin
|
||
deriving Repr, Inhabited
|
||
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
-- §2 Dimension Weights for 14D Semantic Vectors
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
|
||
def dimensionWeights : List Q16_16 :=
|
||
[
|
||
Q16_16.ofFrac 19 10000, -- 0.0019
|
||
Q16_16.ofFrac 20 10000, -- 0.0020
|
||
Q16_16.ofFrac 24 10000, -- 0.0024
|
||
Q16_16.ofFrac 25 10000, -- 0.0025
|
||
Q16_16.ofFrac 23 10000, -- 0.0023
|
||
Q16_16.ofFrac 16 10000, -- 0.0016
|
||
Q16_16.ofFrac 19 10000, -- 0.0019
|
||
Q16_16.ofFrac 18 10000, -- 0.0018
|
||
Q16_16.ofFrac 20 10000, -- 0.0020
|
||
Q16_16.ofFrac 25 10000, -- 0.0025
|
||
Q16_16.ofFrac 18 10000, -- 0.0018
|
||
Q16_16.ofFrac 22 10000, -- 0.0022
|
||
Q16_16.ofFrac 21 10000, -- 0.0021
|
||
Q16_16.ofFrac 26 10000 -- 0.0026 (dominant dimension)
|
||
]
|
||
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
-- §3 Encoding/Decoding Operations
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
|
||
/-- Encode 14D semantic vector to Poincaré disk coordinates -/
|
||
def encodeToPoincare (vector : List Q16_16) : HyperbolicVector :=
|
||
if vector.length ≠ 14 then
|
||
{ x := Q16_16.zero, y := Q16_16.zero, dimension := 14, norm := Q16_16.zero }
|
||
else
|
||
-- Weighted projection to 2D using even/odd indices
|
||
let xSum := List.foldl (fun acc (i : Nat) =>
|
||
if i % 2 = 0 then
|
||
let weight := List.getD dimensionWeights i Q16_16.zero
|
||
let val := List.getD vector i Q16_16.zero
|
||
{ raw := acc.raw + (val.raw * weight.raw) / 65536 }
|
||
else
|
||
acc
|
||
) Q16_16.zero (List.range 14)
|
||
|
||
let ySum := List.foldl (fun acc (i : Nat) =>
|
||
if i % 2 = 1 then
|
||
let weight := List.getD dimensionWeights i Q16_16.zero
|
||
let val := List.getD vector i Q16_16.zero
|
||
{ raw := acc.raw + (val.raw * weight.raw) / 65536 }
|
||
else
|
||
acc
|
||
) Q16_16.zero (List.range 14)
|
||
|
||
-- Simplified norm calculation (avoid sqrt for fixed-point)
|
||
let normSquared := (xSum.raw * xSum.raw + ySum.raw * ySum.raw) / 65536
|
||
let norm := Q16_16.ofFrac normSquared.toNat 65536
|
||
|
||
{
|
||
x := xSum,
|
||
y := ySum,
|
||
dimension := 14,
|
||
norm := norm
|
||
}
|
||
|
||
/-- Decode from Poincaré disk back to 14D vector space -/
|
||
def decodeFromPoincare (hyperbolic : HyperbolicVector) : List Q16_16 :=
|
||
-- Expand back to 14D using inverse projection
|
||
let evenWeightSum := List.foldl (fun acc (_w : Q16_16) =>
|
||
{ raw := acc.raw + _w.raw }
|
||
) Q16_16.zero (List.zipWith (fun w i => if i % 2 = 0 then w else Q16_16.zero) dimensionWeights (List.range 14))
|
||
|
||
let oddWeightSum := List.foldl (fun acc (_w : Q16_16) =>
|
||
{ raw := acc.raw + _w.raw }
|
||
) Q16_16.zero (List.zipWith (fun w i => if i % 2 = 1 then w else Q16_16.zero) dimensionWeights (List.range 14))
|
||
|
||
let xContrib := if evenWeightSum.raw = 0 then Q16_16.zero else { raw := (hyperbolic.x.raw * 65536) / (evenWeightSum.raw + 1) }
|
||
let yContrib := if oddWeightSum.raw = 0 then Q16_16.zero else { raw := (hyperbolic.y.raw * 65536) / (oddWeightSum.raw + 1) }
|
||
|
||
List.zipWith (fun _w i =>
|
||
if i % 2 = 0 then
|
||
let weight := List.getD dimensionWeights i Q16_16.zero
|
||
if evenWeightSum.raw = 0 then Q16_16.zero else { raw := (xContrib.raw * weight.raw) / 65536 }
|
||
else
|
||
let weight := List.getD dimensionWeights i Q16_16.zero
|
||
if oddWeightSum.raw = 0 then Q16_16.zero else { raw := (yContrib.raw * weight.raw) / 65536 }
|
||
) dimensionWeights (List.range 14)
|
||
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
-- §4 Möbius Transformation
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
|
||
/-- Apply Möbius transformation to point z in Poincaré disk -/
|
||
def mobiusTransform (a z : HyperbolicVector) : HyperbolicVector :=
|
||
let aNormSq := (a.x.raw * a.x.raw + a.y.raw * a.y.raw) / 65536
|
||
let zNormSq := (z.x.raw * z.x.raw + z.y.raw * z.y.raw) / 65536
|
||
let az := (a.x.raw * z.x.raw + a.y.raw * z.y.raw) / 65536
|
||
|
||
-- Möbius transformation formula
|
||
let numeratorX := ((65536 + 2*az + aNormSq) * z.x.raw + (65536 + zNormSq) * a.x.raw) / 65536
|
||
let numeratorY := ((65536 + 2*az + aNormSq) * z.y.raw + (65536 + zNormSq) * a.y.raw) / 65536
|
||
let denominator := (65536 + 2*az + aNormSq + zNormSq)
|
||
|
||
let newX := if denominator = 0 then Q16_16.zero else { raw := (numeratorX * 65536) / denominator }
|
||
let newY := if denominator = 0 then Q16_16.zero else { raw := (numeratorY * 65536) / denominator }
|
||
|
||
let newNormSq := (newX.raw * newX.raw + newY.raw * newY.raw) / 65536
|
||
let newNorm := Q16_16.ofFrac newNormSq.toNat 65536
|
||
|
||
{
|
||
x := newX,
|
||
y := newY,
|
||
dimension := 2,
|
||
norm := newNorm
|
||
}
|
||
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
-- §5 Hyperbolic Distance
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
|
||
/-- Compute hyperbolic distance between two points in Poincaré disk -/
|
||
def hyperbolicDistance (x y : HyperbolicVector) : Q16_16 :=
|
||
let xNormSq := (x.x.raw * x.x.raw + x.y.raw * x.y.raw) / 65536
|
||
let yNormSq := (y.x.raw * y.x.raw + y.y.raw * y.y.raw) / 65536
|
||
let diffX := x.x.raw - y.x.raw
|
||
let diffY := x.y.raw - y.y.raw
|
||
let diffNormSq := (diffX * diffX + diffY * diffY) / 65536
|
||
|
||
let numerator := 2 * diffNormSq
|
||
let denominator := (65536 - xNormSq) * (65536 - yNormSq) / 65536
|
||
|
||
if denominator = 0 then
|
||
Q16_16.one
|
||
else
|
||
let ratio := (numerator * 65536) / denominator
|
||
-- Simplified: return ratio as distance approximation
|
||
-- In production, would use acosh(1 + ratio)
|
||
Q16_16.ofFrac ratio.toNat 65536
|
||
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
-- §6 Cache Structure
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
|
||
structure HyperbolicCache where
|
||
entries : List (String × HyperbolicVector)
|
||
deriving Repr, Inhabited
|
||
|
||
/-- Initialize empty hyperbolic cache -/
|
||
def initHyperbolicCache : HyperbolicCache :=
|
||
{ entries := [] }
|
||
|
||
/-- Get or encode vector from cache -/
|
||
def getOrEncode (cache : HyperbolicCache) (vector : List Q16_16) (key : String) : HyperbolicCache × HyperbolicVector :=
|
||
let existing := List.find? (·.1 = key) cache.entries
|
||
match existing with
|
||
| some (_, hv) => (cache, hv)
|
||
| none =>
|
||
let hv := encodeToPoincare vector
|
||
({ entries := (key, hv) :: cache.entries }, hv)
|
||
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
-- §7 Theorems
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
|
||
/-- Dimension weights list has 14 elements -/
|
||
theorem dimensionWeightsLength : dimensionWeights.length = 14 := by
|
||
rfl
|
||
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
-- §8 Example Usage
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
|
||
#eval let vector := List.replicate 14 (Q16_16.ofFrac 20 10000)
|
||
encodeToPoincare vector
|
||
|
||
#eval let hv := { x := Q16_16.ofFrac 30 100, y := Q16_16.ofFrac 40 100, dimension := 14, norm := Q16_16.ofFrac 50 100 }
|
||
decodeFromPoincare hv
|
||
|
||
#eval let a := { x := Q16_16.ofFrac 30 100, y := Q16_16.ofFrac 20 100, dimension := 2, norm := Q16_16.ofFrac 36 100 }
|
||
let z := { x := Q16_16.ofFrac 50 100, y := Q16_16.ofFrac 40 100, dimension := 2, norm := Q16_16.ofFrac 64 100 }
|
||
mobiusTransform a z
|
||
|
||
#eval let x := { x := Q16_16.ofFrac 10 100, y := Q16_16.ofFrac 10 100, dimension := 2, norm := Q16_16.ofFrac 14 100 }
|
||
let y := { x := Q16_16.ofFrac 20 100, y := Q16_16.ofFrac 20 100, dimension := 2, norm := Q16_16.ofFrac 28 100 }
|
||
hyperbolicDistance x y
|
||
|
||
end Semantics.HyperbolicEncoding
|