UNPKG

claude-flow

Version:

Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration

1,687 lines (1,488 loc) 56.8 kB
/** * RuVector PostgreSQL Bridge - Hyperbolic Embeddings Module * * Comprehensive hyperbolic geometry support for embedding hierarchical data * (taxonomies, org charts, ASTs, dependency graphs) in non-Euclidean spaces. * * Supports four hyperbolic models: * - Poincare Ball Model: Conformal, good for visualization * - Lorentz (Hyperboloid) Model: Numerically stable, good for optimization * - Klein Model: Straight geodesics, good for convex optimization * - Half-Space Model: Upper half-plane, good for theoretical analysis * * @module @claude-flow/plugins/integrations/ruvector/hyperbolic * @version 1.0.0 */ import type { HyperbolicModel, HyperbolicEmbedding, HyperbolicOperation, } from './types.js'; // ============================================================================ // Constants and Configuration // ============================================================================ /** * Default numerical stability epsilon */ const DEFAULT_EPS = 1e-15; /** * Maximum norm for Poincare ball to maintain stability (must be < 1) */ const DEFAULT_MAX_NORM = 1 - 1e-5; /** * Default curvature for hyperbolic space (negative value) */ const DEFAULT_CURVATURE = -1.0; // ============================================================================ // Utility Functions // ============================================================================ /** * Computes the dot product of two vectors. */ function dot(a: number[], b: number[]): number { let sum = 0; for (let i = 0; i < a.length; i++) { sum += a[i] * b[i]; } return sum; } /** * Computes the Euclidean (L2) norm of a vector. */ function norm(v: number[]): number { return Math.sqrt(dot(v, v)); } /** * Computes the squared norm of a vector. */ function normSquared(v: number[]): number { return dot(v, v); } /** * Scales a vector by a scalar. */ function scale(v: number[], s: number): number[] { return v.map((x) => x * s); } /** * Adds two vectors. */ function add(a: number[], b: number[]): number[] { return a.map((x, i) => x + b[i]); } /** * Subtracts vector b from vector a. */ function sub(a: number[], b: number[]): number[] { return a.map((x, i) => x - b[i]); } /** * Clamps a value to a range. */ function clamp(value: number, min: number, max: number): number { return Math.max(min, Math.min(max, value)); } /** * Safe arctanh implementation with clamping. */ function safeAtanh(x: number, eps: number = DEFAULT_EPS): number { const clamped = clamp(x, -1 + eps, 1 - eps); return 0.5 * Math.log((1 + clamped) / (1 - clamped)); } /** * Safe arccosh implementation. */ function safeAcosh(x: number, eps: number = DEFAULT_EPS): number { return Math.acosh(Math.max(1 + eps, x)); } /** * Creates a zero vector of specified dimension. */ function zeros(dim: number): number[] { return new Array(dim).fill(0); } // ============================================================================ // Hyperbolic Space Configuration // ============================================================================ /** * Configuration for a hyperbolic space instance. */ export interface HyperbolicSpaceConfig { /** Hyperbolic model to use */ readonly model: HyperbolicModel; /** Curvature parameter (negative for hyperbolic space) */ readonly curvature: number; /** Embedding dimension */ readonly dimension: number; /** Numerical stability epsilon */ readonly eps?: number; /** Maximum norm for Poincare ball */ readonly maxNorm?: number; /** Whether curvature is learnable */ readonly learnCurvature?: boolean; } /** * Result from a hyperbolic distance computation. */ export interface HyperbolicDistanceResult { /** Geodesic distance */ readonly distance: number; /** Model used for computation */ readonly model: HyperbolicModel; /** Effective curvature */ readonly curvature: number; } /** * Result from a hyperbolic search operation. */ export interface HyperbolicSearchResult { /** Point ID */ readonly id: string | number; /** Geodesic distance from query */ readonly distance: number; /** Point coordinates in hyperbolic space */ readonly point: number[]; /** Original metadata */ readonly metadata?: Record<string, unknown>; } /** * Options for batch hyperbolic operations. */ export interface HyperbolicBatchOptions { /** Points to process */ readonly points: number[][]; /** Operation to perform */ readonly operation: HyperbolicOperation; /** Additional operation parameters */ readonly params?: { readonly tangent?: number[]; readonly base?: number[]; readonly target?: number[]; readonly matrix?: number[][]; }; /** Process in parallel */ readonly parallel?: boolean; /** Batch size for processing */ readonly batchSize?: number; } /** * Result from batch hyperbolic operations. */ export interface HyperbolicBatchResult { /** Resulting points/values */ readonly results: number[][]; /** Operation performed */ readonly operation: HyperbolicOperation; /** Processing time in milliseconds */ readonly durationMs: number; /** Number of points processed */ readonly count: number; } // ============================================================================ // HyperbolicSpace Class // ============================================================================ /** * HyperbolicSpace provides comprehensive operations for hyperbolic geometry. * * Supports Poincare ball, Lorentz (hyperboloid), Klein disk, and half-space models. * All operations are numerically stable and handle edge cases gracefully. * * @example * ```typescript * const space = new HyperbolicSpace('poincare', -1.0); * const dist = space.distance([0.1, 0.2], [0.3, 0.4]); * const mapped = space.expMap([0, 0], [0.1, 0.2]); * ``` */ export class HyperbolicSpace { /** Current hyperbolic model */ readonly model: HyperbolicModel; /** Curvature parameter (negative for hyperbolic) */ private _curvature: number; /** Numerical stability epsilon */ readonly eps: number; /** Maximum norm for Poincare ball */ readonly maxNorm: number; /** Scaling factor derived from curvature: sqrt(|c|) */ private _scale: number; /** * Creates a new HyperbolicSpace instance. * * @param model - Hyperbolic model to use * @param curvature - Curvature parameter (should be negative, will be negated if positive) * @param eps - Numerical stability epsilon * @param maxNorm - Maximum norm for Poincare ball */ constructor( model: HyperbolicModel, curvature: number = DEFAULT_CURVATURE, eps: number = DEFAULT_EPS, maxNorm: number = DEFAULT_MAX_NORM ) { this.model = model; this._curvature = curvature > 0 ? -curvature : curvature; this.eps = eps; this.maxNorm = maxNorm; this._scale = Math.sqrt(Math.abs(this._curvature)); } /** * Gets the current curvature. */ get curvature(): number { return this._curvature; } /** * Gets the scaling factor sqrt(|c|). */ get scale(): number { return this._scale; } /** * Updates the curvature (for learnable curvature scenarios). */ setCurvature(c: number): void { this._curvature = c > 0 ? -c : c; this._scale = Math.sqrt(Math.abs(this._curvature)); } // ========================================================================== // Distance Calculations // ========================================================================== /** * Computes the geodesic distance between two points in hyperbolic space. * * The distance formula depends on the model: * - Poincare: d(u,v) = (2/sqrt|c|) * arctanh(sqrt|c| * ||(-u) + v||_M) * - Lorentz: d(u,v) = (1/sqrt|c|) * arcosh(-c * <u,v>_L) * - Klein: Converted to Poincare first * - Half-space: d(u,v) = arcosh(1 + ||u-v||^2 / (2*u_n*v_n)) * * @param a - First point * @param b - Second point * @returns Geodesic distance */ distance(a: number[], b: number[]): number { switch (this.model) { case 'poincare': return this.poincareDistance(a, b); case 'lorentz': return this.lorentzDistance(a, b); case 'klein': return this.kleinDistance(a, b); case 'half_space': return this.halfSpaceDistance(a, b); default: throw new Error(`Unknown hyperbolic model: ${this.model}`); } } /** * Computes distance in the Poincare ball model. * * Formula: d(u,v) = (2/sqrt|c|) * arctanh(sqrt|c| * ||(-u) +_M v||) * * Where +_M is Mobius addition. */ private poincareDistance(u: number[], v: number[]): number { // Use Mobius addition: -u +_M v const negU = scale(u, -1); const diff = this.mobiusAdd(negU, v); const diffNorm = norm(diff); // d = (2/sqrt|c|) * arctanh(sqrt|c| * ||diff||) const scaledNorm = this._scale * diffNorm; return (2 / this._scale) * safeAtanh(scaledNorm, this.eps); } /** * Computes distance in the Lorentz (hyperboloid) model. * * Formula: d(u,v) = (1/sqrt|c|) * arcosh(-c * <u,v>_L) * * Where <u,v>_L is the Lorentz inner product: -u0*v0 + u1*v1 + ... + un*vn */ private lorentzDistance(u: number[], v: number[]): number { const lorentzInner = this.lorentzInnerProduct(u, v); // -c * <u,v>_L for negative curvature const argument = -this._curvature * lorentzInner; return safeAcosh(argument, this.eps) / this._scale; } /** * Computes distance in the Klein model. * Converts to Poincare first for numerical stability. */ private kleinDistance(u: number[], v: number[]): number { const uPoincare = this.kleinToPoincare(u); const vPoincare = this.kleinToPoincare(v); return this.poincareDistance(uPoincare, vPoincare); } /** * Computes distance in the half-space model. * * Formula: d(u,v) = arcosh(1 + ||u-v||^2 / (2*u_n*v_n)) * * Where u_n, v_n are the last coordinates (must be positive). */ private halfSpaceDistance(u: number[], v: number[]): number { const n = u.length - 1; const un = Math.max(u[n], this.eps); const vn = Math.max(v[n], this.eps); const diffSq = normSquared(sub(u, v)); const argument = 1 + diffSq / (2 * un * vn); return safeAcosh(argument, this.eps) / this._scale; } /** * Computes the Lorentz inner product. * <u,v>_L = -u0*v0 + sum(u_i*v_i for i=1..n) */ private lorentzInnerProduct(u: number[], v: number[]): number { let result = -u[0] * v[0]; // Time component (negative) for (let i = 1; i < u.length; i++) { result += u[i] * v[i]; // Spatial components (positive) } return result; } // ========================================================================== // Exponential and Logarithmic Maps // ========================================================================== /** * Exponential map: Maps a tangent vector at a base point to the manifold. * * exp_p(v) moves from point p in the direction of tangent vector v. * * @param base - Base point on the manifold * @param tangent - Tangent vector at the base point * @returns Point on the manifold */ expMap(base: number[], tangent: number[]): number[] { switch (this.model) { case 'poincare': return this.poincareExpMap(base, tangent); case 'lorentz': return this.lorentzExpMap(base, tangent); case 'klein': return this.kleinExpMap(base, tangent); case 'half_space': return this.halfSpaceExpMap(base, tangent); default: throw new Error(`Unknown hyperbolic model: ${this.model}`); } } /** * Logarithmic map: Maps a point on the manifold to a tangent vector at base. * * log_p(q) gives the tangent vector at p pointing towards q. * * @param base - Base point on the manifold * @param point - Target point on the manifold * @returns Tangent vector at the base point */ logMap(base: number[], point: number[]): number[] { switch (this.model) { case 'poincare': return this.poincareLogMap(base, point); case 'lorentz': return this.lorentzLogMap(base, point); case 'klein': return this.kleinLogMap(base, point); case 'half_space': return this.halfSpaceLogMap(base, point); default: throw new Error(`Unknown hyperbolic model: ${this.model}`); } } /** * Poincare exponential map. * * exp_p(v) = p +_M (tanh(sqrt|c| * ||v||_p / 2) * v / (sqrt|c| * ||v||_p)) * * Where ||v||_p is the Poincare tangent norm: lambda_p * ||v|| * And lambda_p = 2 / (1 - |c| * ||p||^2) is the conformal factor. */ private poincareExpMap(base: number[], tangent: number[]): number[] { const tangentNorm = norm(tangent); if (tangentNorm < this.eps) { return [...base]; } // Conformal factor at base const baseSq = normSquared(base); const lambda = 2 / (1 - Math.abs(this._curvature) * baseSq); // Scaled tangent norm const vNorm = lambda * tangentNorm; // Compute direction and scale const t = Math.tanh(this._scale * vNorm / 2); const direction = scale(tangent, t / (this._scale * vNorm)); // Mobius add base + direction return this.projectToManifold(this.mobiusAdd(base, direction)); } /** * Poincare logarithmic map. * * log_p(q) = (2 / (sqrt|c| * lambda_p)) * arctanh(sqrt|c| * ||(-p) +_M q||) * ((-p) +_M q) / ||(-p) +_M q|| */ private poincareLogMap(base: number[], point: number[]): number[] { const negBase = scale(base, -1); const diff = this.mobiusAdd(negBase, point); const diffNorm = norm(diff); if (diffNorm < this.eps) { return zeros(base.length); } // Conformal factor at base const baseSq = normSquared(base); const lambda = 2 / (1 - Math.abs(this._curvature) * baseSq); // Compute coefficient const atanh_arg = this._scale * diffNorm; const coeff = (2 / (this._scale * lambda)) * safeAtanh(atanh_arg, this.eps); // Direction return scale(diff, coeff / diffNorm); } /** * Lorentz exponential map. * * exp_p(v) = cosh(||v||_L) * p + sinh(||v||_L) * v / ||v||_L * * Where ||v||_L is the Lorentz norm: sqrt(<v,v>_L) */ private lorentzExpMap(base: number[], tangent: number[]): number[] { const tangentNormSq = this.lorentzInnerProduct(tangent, tangent); if (tangentNormSq < this.eps * this.eps) { return [...base]; } const tangentNorm = Math.sqrt(Math.max(0, tangentNormSq)); const scaledNorm = this._scale * tangentNorm; const coshVal = Math.cosh(scaledNorm); const sinhVal = Math.sinh(scaledNorm); const result = add( scale(base, coshVal), scale(tangent, sinhVal / tangentNorm) ); return this.projectToManifold(result); } /** * Lorentz logarithmic map. * * log_p(q) = (arcosh(-<p,q>_L) / sqrt(-<p,q>_L^2 - 1)) * (q + <p,q>_L * p) */ private lorentzLogMap(base: number[], point: number[]): number[] { const inner = this.lorentzInnerProduct(base, point); const alpha = -inner; if (alpha <= 1 + this.eps) { return zeros(base.length); } const sqrtArg = Math.sqrt(alpha * alpha - 1); const coeff = safeAcosh(alpha, this.eps) / sqrtArg; // v = q + <p,q>_L * p, but we need q - alpha * p for tangent const tangent = sub(point, scale(base, alpha)); return scale(tangent, coeff); } /** * Klein exponential map (via Poincare). */ private kleinExpMap(base: number[], tangent: number[]): number[] { const basePoincare = this.kleinToPoincare(base); const tangentPoincare = this.kleinTangentToPoincare(base, tangent); const resultPoincare = this.poincareExpMap(basePoincare, tangentPoincare); return this.poincareToKlein(resultPoincare); } /** * Klein logarithmic map (via Poincare). */ private kleinLogMap(base: number[], point: number[]): number[] { const basePoincare = this.kleinToPoincare(base); const pointPoincare = this.kleinToPoincare(point); const tangentPoincare = this.poincareLogMap(basePoincare, pointPoincare); return this.poincareTangentToKlein(base, tangentPoincare); } /** * Half-space exponential map. */ private halfSpaceExpMap(base: number[], tangent: number[]): number[] { // For half-space, use the Riemannian metric g = (1/x_n^2) * I const n = base.length - 1; const xn = Math.max(base[n], this.eps); // Scale tangent by conformal factor const scaledTangent = scale(tangent, xn); const tangentNorm = norm(scaledTangent); if (tangentNorm < this.eps) { return [...base]; } // Geodesic in half-space model const t = tangentNorm * this._scale; const direction = scale(scaledTangent, 1 / tangentNorm); const result = add(base, scale(direction, Math.sinh(t) * xn / this._scale)); result[n] = xn * Math.cosh(t); return this.projectToManifold(result); } /** * Half-space logarithmic map. */ private halfSpaceLogMap(base: number[], point: number[]): number[] { const n = base.length - 1; const xn = Math.max(base[n], this.eps); const yn = Math.max(point[n], this.eps); const diff = sub(point, base); const diffSq = normSquared(diff); const argument = 1 + diffSq / (2 * xn * yn); const dist = safeAcosh(argument, this.eps); if (dist < this.eps) { return zeros(base.length); } // Compute initial tangent direction const direction = scale(diff, 1 / Math.sqrt(diffSq + this.eps)); return scale(direction, dist * xn / this._scale); } // ========================================================================== // Mobius Operations (Poincare Ball) // ========================================================================== /** * Mobius addition in the Poincare ball. * * a +_M b = ((1 + 2c<a,b> + c||b||^2)a + (1 - c||a||^2)b) / (1 + 2c<a,b> + c^2||a||^2||b||^2) * * @param a - First point * @param b - Second point * @returns Mobius sum */ mobiusAdd(a: number[], b: number[]): number[] { const c = Math.abs(this._curvature); const aSq = normSquared(a); const bSq = normSquared(b); const ab = dot(a, b); const numerator1 = 1 + 2 * c * ab + c * bSq; const numerator2 = 1 - c * aSq; const denominator = 1 + 2 * c * ab + c * c * aSq * bSq; const safeD = Math.max(denominator, this.eps); const result = add( scale(a, numerator1 / safeD), scale(b, numerator2 / safeD) ); return this.projectToManifold(result); } /** * Mobius matrix-vector multiplication. * * M otimes_M v = tanh(||Mv|| / ||v|| * arctanh(||v||)) * Mv / ||Mv|| * * This applies a matrix transformation in hyperbolic space. * * @param matrix - Transformation matrix * @param vec - Vector in Poincare ball * @returns Transformed vector */ mobiusMatVec(matrix: number[][], vec: number[]): number[] { // First, compute Mv in Euclidean space const mv: number[] = []; for (let i = 0; i < matrix.length; i++) { let sum = 0; for (let j = 0; j < vec.length; j++) { sum += matrix[i][j] * vec[j]; } mv.push(sum); } const mvNorm = norm(mv); const vNorm = norm(vec); if (vNorm < this.eps || mvNorm < this.eps) { return this.projectToManifold(mv); } // Apply hyperbolic scaling const scaledVNorm = this._scale * vNorm; const atanhV = safeAtanh(scaledVNorm, this.eps); const scaleFactor = Math.tanh(mvNorm / vNorm * atanhV) / (this._scale * mvNorm); return this.projectToManifold(scale(mv, scaleFactor)); } /** * Mobius scalar multiplication. * * r *_M x = tanh(r * arctanh(sqrt|c| * ||x||)) * x / (sqrt|c| * ||x||) * * @param r - Scalar multiplier * @param x - Point in Poincare ball * @returns Scaled point */ mobiusScalarMul(r: number, x: number[]): number[] { const xNorm = norm(x); if (xNorm < this.eps) { return zeros(x.length); } const scaledNorm = this._scale * xNorm; const atanhX = safeAtanh(scaledNorm, this.eps); const newNorm = Math.tanh(r * atanhX) / this._scale; return this.projectToManifold(scale(x, newNorm / xNorm)); } // ========================================================================== // Projection Operations // ========================================================================== /** * Projects a point onto the hyperbolic manifold. * * For Poincare: Ensures ||x|| < maxNorm * For Lorentz: Ensures x is on the hyperboloid * For Klein: Ensures ||x|| < 1 * For Half-space: Ensures last coordinate > eps * * @param point - Point to project * @returns Point on the manifold */ projectToManifold(point: number[]): number[] { switch (this.model) { case 'poincare': return this.projectToPoincare(point); case 'lorentz': return this.projectToLorentz(point); case 'klein': return this.projectToKlein(point); case 'half_space': return this.projectToHalfSpace(point); default: throw new Error(`Unknown hyperbolic model: ${this.model}`); } } /** * Projects onto the Poincare ball (||x|| < maxNorm). */ private projectToPoincare(point: number[]): number[] { const n = norm(point); if (n >= this.maxNorm) { return scale(point, (this.maxNorm - this.eps) / n); } return point; } /** * Projects onto the Lorentz hyperboloid. * Ensures <x,x>_L = -1/c */ private projectToLorentz(point: number[]): number[] { // Compute spatial norm let spatialSq = 0; for (let i = 1; i < point.length; i++) { spatialSq += point[i] * point[i]; } // Time component: x0 = sqrt(1/|c| + spatial^2) const x0 = Math.sqrt(1 / Math.abs(this._curvature) + spatialSq); const result = [...point]; result[0] = x0; return result; } /** * Projects onto the Klein disk (||x|| < 1). */ private projectToKlein(point: number[]): number[] { const n = norm(point); if (n >= 1 - this.eps) { return scale(point, (1 - 2 * this.eps) / n); } return point; } /** * Projects onto the half-space (last coordinate > eps). */ private projectToHalfSpace(point: number[]): number[] { const result = [...point]; const lastIdx = point.length - 1; result[lastIdx] = Math.max(result[lastIdx], this.eps); return result; } /** * Projects a vector onto the tangent space at a given base point. * * @param base - Base point on the manifold * @param vec - Vector to project * @returns Vector in the tangent space */ projectToTangent(base: number[], vec: number[]): number[] { switch (this.model) { case 'poincare': // In Poincare ball, tangent space is R^n (no projection needed for vectors) return vec; case 'lorentz': return this.projectToLorentzTangent(base, vec); case 'klein': return vec; case 'half_space': return vec; default: throw new Error(`Unknown hyperbolic model: ${this.model}`); } } /** * Projects onto the Lorentz tangent space. * Tangent vectors must satisfy <p, v>_L = 0. */ private projectToLorentzTangent(base: number[], vec: number[]): number[] { const inner = this.lorentzInnerProduct(base, vec); const baseInner = this.lorentzInnerProduct(base, base); const coeff = inner / Math.min(baseInner, -this.eps); return sub(vec, scale(base, coeff)); } // ========================================================================== // Model Conversions // ========================================================================== /** * Converts a point from Poincare ball to Lorentz hyperboloid. * * Lorentz: (x0, x1, ..., xn) where x0 is the time component * x0 = (1 + |c| * ||p||^2) / (1 - |c| * ||p||^2) * xi = 2 * sqrt|c| * pi / (1 - |c| * ||p||^2) * * @param poincare - Point in Poincare ball * @returns Point on Lorentz hyperboloid */ toLorentz(poincare: number[]): number[] { const c = Math.abs(this._curvature); const pSq = normSquared(poincare); const denom = Math.max(1 - c * pSq, this.eps); const x0 = (1 + c * pSq) / denom; const lorentz = [x0]; for (let i = 0; i < poincare.length; i++) { lorentz.push((2 * this._scale * poincare[i]) / denom); } return lorentz; } /** * Converts a point from Lorentz hyperboloid to Poincare ball. * * pi = xi / (sqrt|c| * (x0 + 1)) * * @param lorentz - Point on Lorentz hyperboloid * @returns Point in Poincare ball */ toPoincare(lorentz: number[]): number[] { const denom = this._scale * (lorentz[0] + 1); const poincare: number[] = []; for (let i = 1; i < lorentz.length; i++) { poincare.push(lorentz[i] / Math.max(denom, this.eps)); } return this.projectToPoincare(poincare); } /** * Converts a point from Klein disk to Poincare ball. * * pi = ki / (1 + sqrt(1 - |c| * ||k||^2)) * * @param klein - Point in Klein disk * @returns Point in Poincare ball */ kleinToPoincare(klein: number[]): number[] { const c = Math.abs(this._curvature); const kSq = normSquared(klein); const sqrtArg = Math.sqrt(Math.max(1 - c * kSq, this.eps)); const denom = 1 + sqrtArg; return this.projectToPoincare(scale(klein, 1 / denom)); } /** * Converts a point from Poincare ball to Klein disk. * * ki = 2 * pi / (1 + |c| * ||p||^2) * * @param poincare - Point in Poincare ball * @returns Point in Klein disk */ poincareToKlein(poincare: number[]): number[] { const c = Math.abs(this._curvature); const pSq = normSquared(poincare); const factor = 2 / (1 + c * pSq); return this.projectToKlein(scale(poincare, factor)); } /** * Converts a tangent vector from Klein to Poincare. */ private kleinTangentToPoincare(kleinBase: number[], kleinTangent: number[]): number[] { const c = Math.abs(this._curvature); const kSq = normSquared(kleinBase); const sqrtArg = Math.sqrt(Math.max(1 - c * kSq, this.eps)); const factor = sqrtArg / (1 + sqrtArg); return scale(kleinTangent, factor); } /** * Converts a tangent vector from Poincare to Klein. */ private poincareTangentToKlein(kleinBase: number[], poincareTangent: number[]): number[] { const c = Math.abs(this._curvature); const kSq = normSquared(kleinBase); const sqrtArg = Math.sqrt(Math.max(1 - c * kSq, this.eps)); const factor = (1 + sqrtArg) / sqrtArg; return scale(poincareTangent, factor); } /** * Converts a point from Poincare ball to half-space model. * * @param poincare - Point in Poincare ball * @returns Point in half-space model */ poincareToHalfSpace(poincare: number[]): number[] { const c = Math.abs(this._curvature); const n = poincare.length; const pSq = normSquared(poincare); const pn = poincare[n - 1]; const denom = pSq + 2 * pn / this._scale + 1 / c; const safeDenom = Math.max(Math.abs(denom), this.eps) * Math.sign(denom || 1); const result: number[] = []; for (let i = 0; i < n - 1; i++) { result.push(poincare[i] / safeDenom); } result.push((1 - c * pSq) / (2 * this._scale * safeDenom)); return this.projectToHalfSpace(result); } /** * Converts a point from half-space model to Poincare ball. * * @param halfSpace - Point in half-space model * @returns Point in Poincare ball */ halfSpaceToPoincare(halfSpace: number[]): number[] { const n = halfSpace.length; const xn = Math.max(halfSpace[n - 1], this.eps); let xSq = 0; for (let i = 0; i < n - 1; i++) { xSq += halfSpace[i] * halfSpace[i]; } const denom = xSq + (xn + 1 / this._scale) * (xn + 1 / this._scale); const safeDenom = Math.max(denom, this.eps); const result: number[] = []; for (let i = 0; i < n - 1; i++) { result.push((2 * halfSpace[i]) / safeDenom); } result.push((xSq + xn * xn - 1 / Math.abs(this._curvature)) / safeDenom); return this.projectToPoincare(result); } // ========================================================================== // Additional Operations // ========================================================================== /** * Computes the geodesic midpoint of two points. * * @param a - First point * @param b - Second point * @returns Midpoint on the geodesic */ midpoint(a: number[], b: number[]): number[] { // Use exponential map from a with half the tangent to b const tangent = this.logMap(a, b); const halfTangent = scale(tangent, 0.5); return this.expMap(a, halfTangent); } /** * Computes the Frechet mean (centroid) of multiple points. * * Uses iterative gradient descent on the sum of squared distances. * * @param points - Array of points * @param maxIter - Maximum iterations * @param tol - Convergence tolerance * @returns Frechet mean */ centroid(points: number[][], maxIter: number = 100, tol: number = 1e-8): number[] { if (points.length === 0) { throw new Error('Cannot compute centroid of empty set'); } if (points.length === 1) { return [...points[0]]; } // Initialize with Euclidean mean, projected onto manifold let mean = zeros(points[0].length); for (const p of points) { mean = add(mean, p); } mean = this.projectToManifold(scale(mean, 1 / points.length)); // Iterative refinement for (let iter = 0; iter < maxIter; iter++) { // Compute sum of log maps let gradSum = zeros(points[0].length); for (const p of points) { const logP = this.logMap(mean, p); gradSum = add(gradSum, logP); } // Average gradient const avgGrad = scale(gradSum, 1 / points.length); const gradNorm = norm(avgGrad); if (gradNorm < tol) { break; } // Move mean in the direction of gradient mean = this.expMap(mean, avgGrad); } return mean; } /** * Parallel transports a tangent vector along a geodesic. * * @param vector - Tangent vector to transport * @param start - Starting point * @param end - Ending point * @returns Transported vector at the end point */ parallelTransport(vector: number[], start: number[], end: number[]): number[] { switch (this.model) { case 'poincare': return this.poincareParallelTransport(vector, start, end); case 'lorentz': return this.lorentzParallelTransport(vector, start, end); default: // For Klein and half-space, convert via Poincare return this.poincareParallelTransport(vector, start, end); } } /** * Parallel transport in Poincare ball. */ private poincareParallelTransport(vector: number[], start: number[], end: number[]): number[] { const c = Math.abs(this._curvature); // Compute conformal factors const startSq = normSquared(start); const endSq = normSquared(end); const lambdaStart = 2 / (1 - c * startSq); const lambdaEnd = 2 / (1 - c * endSq); // Gyration-based transport const negStart = scale(start, -1); const transported = this.mobiusAdd(end, this.mobiusAdd(negStart, scale(vector, 1))); // Scale by ratio of conformal factors const scaleFactor = lambdaStart / lambdaEnd; return scale(sub(transported, end), scaleFactor); } /** * Parallel transport in Lorentz model. */ private lorentzParallelTransport(vector: number[], start: number[], end: number[]): number[] { const logV = this.logMap(start, end); const logNorm = Math.sqrt(Math.max(0, this.lorentzInnerProduct(logV, logV))); if (logNorm < this.eps) { return [...vector]; } const inner1 = this.lorentzInnerProduct(end, vector); const inner2 = this.lorentzInnerProduct(start, vector); // Note: inner3 = this.lorentzInnerProduct(start, end) is used implicitly in the formula // via the geodesic distance relationship const coeff = (inner1 - inner2 * Math.cosh(this._scale * logNorm)) / Math.sinh(this._scale * logNorm) / logNorm; return add(vector, scale(add(start, scale(end, -1)), coeff)); } /** * Computes a point along the geodesic from a to b at parameter t. * * @param a - Starting point * @param b - Ending point * @param t - Parameter in [0, 1] * @returns Point on geodesic */ geodesic(a: number[], b: number[], t: number): number[] { const tangent = this.logMap(a, b); const scaledTangent = scale(tangent, t); return this.expMap(a, scaledTangent); } /** * Computes the conformal factor (lambda) at a point in Poincare ball. * * lambda_p = 2 / (1 - |c| * ||p||^2) * * @param point - Point in Poincare ball * @returns Conformal factor */ conformalFactor(point: number[]): number { if (this.model !== 'poincare') { throw new Error('Conformal factor is only defined for Poincare ball model'); } const c = Math.abs(this._curvature); const pSq = normSquared(point); return 2 / Math.max(1 - c * pSq, this.eps); } } // ============================================================================ // SQL Generation for RuVector PostgreSQL Functions // ============================================================================ /** * SQL function call builder for RuVector hyperbolic operations. */ export class HyperbolicSQL { /** * Generates SQL for Poincare distance computation. * * @param column - Vector column name * @param query - Query vector * @param curvature - Curvature parameter * @returns SQL expression */ static poincareDistance(column: string, query: number[], curvature: number): string { const vectorStr = `'[${query.join(',')}]'::vector`; return `ruvector_poincare_distance(${column}, ${vectorStr}, ${curvature})`; } /** * Generates SQL for Lorentz distance computation. * * @param column - Vector column name * @param query - Query vector (with time component first) * @param curvature - Curvature parameter * @returns SQL expression */ static lorentzDistance(column: string, query: number[], curvature: number): string { const vectorStr = `'[${query.join(',')}]'::vector`; return `ruvector_lorentz_distance(${column}, ${vectorStr}, ${curvature})`; } /** * Generates SQL for exponential map. * * @param baseColumn - Base point column * @param tangentColumn - Tangent vector column * @param model - Hyperbolic model * @param curvature - Curvature parameter * @returns SQL expression */ static expMap( baseColumn: string, tangentColumn: string, model: HyperbolicModel, curvature: number ): string { const funcName = model === 'lorentz' ? 'ruvector_lorentz_exp_map' : 'ruvector_poincare_exp_map'; return `${funcName}(${baseColumn}, ${tangentColumn}, ${curvature})`; } /** * Generates SQL for logarithmic map. * * @param baseColumn - Base point column * @param targetColumn - Target point column * @param model - Hyperbolic model * @param curvature - Curvature parameter * @returns SQL expression */ static logMap( baseColumn: string, targetColumn: string, model: HyperbolicModel, curvature: number ): string { const funcName = model === 'lorentz' ? 'ruvector_lorentz_log_map' : 'ruvector_poincare_log_map'; return `${funcName}(${baseColumn}, ${targetColumn}, ${curvature})`; } /** * Generates SQL for Mobius addition. * * @param aColumn - First point column * @param bColumn - Second point column * @param curvature - Curvature parameter * @returns SQL expression */ static mobiusAdd(aColumn: string, bColumn: string, curvature: number): string { return `ruvector_poincare_mobius_add(${aColumn}, ${bColumn}, ${curvature})`; } /** * Generates SQL for Mobius matrix-vector multiplication. * * @param matrixColumn - Matrix column * @param vectorColumn - Vector column * @param curvature - Curvature parameter * @returns SQL expression */ static mobiusMatVec(matrixColumn: string, vectorColumn: string, curvature: number): string { return `ruvector_poincare_mobius_matvec(${matrixColumn}, ${vectorColumn}, ${curvature})`; } /** * Generates SQL for parallel transport. * * @param vectorColumn - Vector to transport * @param startColumn - Starting point * @param endColumn - Ending point * @param model - Hyperbolic model * @param curvature - Curvature parameter * @returns SQL expression */ static parallelTransport( vectorColumn: string, startColumn: string, endColumn: string, model: HyperbolicModel, curvature: number ): string { const funcName = model === 'lorentz' ? 'ruvector_lorentz_parallel_transport' : 'ruvector_poincare_parallel_transport'; return `${funcName}(${vectorColumn}, ${startColumn}, ${endColumn}, ${curvature})`; } /** * Generates SQL for computing hyperbolic centroid. * * @param column - Vector column name * @param model - Hyperbolic model * @param curvature - Curvature parameter * @returns SQL expression (aggregate function) */ static centroid(column: string, model: HyperbolicModel, curvature: number): string { const funcName = model === 'lorentz' ? 'ruvector_lorentz_centroid' : 'ruvector_poincare_centroid'; return `${funcName}(${column}, ${curvature})`; } /** * Generates SQL for model conversion (Poincare to Lorentz). * * @param column - Vector column * @param curvature - Curvature parameter * @returns SQL expression */ static poincareToLorentz(column: string, curvature: number): string { return `ruvector_poincare_to_lorentz(${column}, ${curvature})`; } /** * Generates SQL for model conversion (Lorentz to Poincare). * * @param column - Vector column * @param curvature - Curvature parameter * @returns SQL expression */ static lorentzToPoincare(column: string, curvature: number): string { return `ruvector_lorentz_to_poincare(${column}, ${curvature})`; } /** * Generates SQL for hyperbolic nearest neighbor search. * * @param tableName - Table name * @param vectorColumn - Vector column name * @param query - Query vector * @param k - Number of neighbors * @param model - Hyperbolic model * @param curvature - Curvature parameter * @param whereClause - Optional WHERE clause * @returns Complete SQL query */ static nearestNeighbors( tableName: string, vectorColumn: string, query: number[], k: number, model: HyperbolicModel, curvature: number, whereClause?: string ): string { const distFunc = model === 'lorentz' ? this.lorentzDistance(vectorColumn, query, curvature) : this.poincareDistance(vectorColumn, query, curvature); const where = whereClause ? `WHERE ${whereClause}` : ''; return ` SELECT *, ${distFunc} AS hyperbolic_distance FROM ${tableName} ${where} ORDER BY hyperbolic_distance ASC LIMIT ${k} `.trim(); } /** * Generates SQL for creating a hyperbolic embedding column. * * @param tableName - Table name * @param columnName - New column name * @param dimension - Vector dimension * @param model - Hyperbolic model * @returns SQL statement */ static createColumn( tableName: string, columnName: string, dimension: number, model: HyperbolicModel ): string { const comment = `Hyperbolic embedding (${model} model, dim=${dimension})`; return ` ALTER TABLE ${tableName} ADD COLUMN IF NOT EXISTS ${columnName} vector(${dimension}); COMMENT ON COLUMN ${tableName}.${columnName} IS '${comment}'; `.trim(); } /** * Generates SQL for batch hyperbolic distance computation. * * @param tableName - Table name * @param vectorColumn - Vector column name * @param queries - Array of query vectors * @param k - Number of neighbors per query * @param model - Hyperbolic model * @param curvature - Curvature parameter * @returns SQL query using LATERAL join */ static batchNearestNeighbors( tableName: string, vectorColumn: string, queries: number[][], k: number, model: HyperbolicModel, curvature: number ): string { const queryValues = queries .map((q, i) => `(${i}, '[${q.join(',')}]'::vector)`) .join(',\n '); const distFunc = model === 'lorentz' ? `ruvector_lorentz_distance(t.${vectorColumn}, q.query_vec, ${curvature})` : `ruvector_poincare_distance(t.${vectorColumn}, q.query_vec, ${curvature})`; return ` WITH queries(query_id, query_vec) AS ( VALUES ${queryValues} ) SELECT q.query_id, results.* FROM queries q CROSS JOIN LATERAL ( SELECT t.*, ${distFunc} AS hyperbolic_distance FROM ${tableName} t ORDER BY ${distFunc} ASC LIMIT ${k} ) results ORDER BY q.query_id, results.hyperbolic_distance ASC `.trim(); } } // ============================================================================ // Batch Operations for Embeddings // ============================================================================ /** * HyperbolicBatchProcessor handles batch operations on hyperbolic embeddings. */ export class HyperbolicBatchProcessor { private readonly space: HyperbolicSpace; constructor(model: HyperbolicModel, curvature: number = DEFAULT_CURVATURE) { this.space = new HyperbolicSpace(model, curvature); } /** * Computes distances from a query point to multiple target points. * * @param query - Query point * @param targets - Array of target points * @returns Array of distances */ batchDistance(query: number[], targets: number[][]): number[] { return targets.map((target) => this.space.distance(query, target)); } /** * Applies exponential map to multiple tangent vectors from a base point. * * @param base - Base point * @param tangents - Array of tangent vectors * @returns Array of resulting points */ batchExpMap(base: number[], tangents: number[][]): number[][] { return tangents.map((tangent) => this.space.expMap(base, tangent)); } /** * Applies logarithmic map from a base point to multiple target points. * * @param base - Base point * @param targets - Array of target points * @returns Array of tangent vectors */ batchLogMap(base: number[], targets: number[][]): number[][] { return targets.map((target) => this.space.logMap(base, target)); } /** * Projects multiple points onto the manifold. * * @param points - Array of points to project * @returns Array of projected points */ batchProject(points: number[][]): number[][] { return points.map((point) => this.space.projectToManifold(point)); } /** * Converts multiple points between models. * * @param points - Array of points * @param fromModel - Source model * @param toModel - Target model * @returns Array of converted points */ batchConvert( points: number[][], fromModel: HyperbolicModel, toModel: HyperbolicModel ): number[][] { if (fromModel === toModel) { return points.map((p) => [...p]); } // Handle direct conversions if (fromModel === 'poincare' && toModel === 'lorentz') { return points.map((p) => this.space.toLorentz(p)); } if (fromModel === 'lorentz' && toModel === 'poincare') { return points.map((p) => this.space.toPoincare(p)); } // For other conversions, go through Poincare as intermediate let intermediate = points; // First convert to Poincare if (fromModel === 'lorentz') { intermediate = points.map((p) => this.space.toPoincare(p)); } else if (fromModel === 'klein') { intermediate = points.map((p) => this.space.kleinToPoincare(p)); } else if (fromModel === 'half_space') { intermediate = points.map((p) => this.space.halfSpaceToPoincare(p)); } // Then convert from Poincare to target if (toModel === 'lorentz') { return intermediate.map((p) => this.space.toLorentz(p)); } else if (toModel === 'klein') { return intermediate.map((p) => this.space.poincareToKlein(p)); } else if (toModel === 'half_space') { return intermediate.map((p) => this.space.poincareToHalfSpace(p)); } return intermediate; } /** * Performs k-nearest neighbor search in hyperbolic space. * * @param query - Query point * @param points - Array of candidate points with IDs * @param k - Number of neighbors * @returns K nearest neighbors sorted by distance */ knnSearch( query: number[], points: Array<{ id: string | number; point: number[]; metadata?: Record<string, unknown> }>, k: number ): HyperbolicSearchResult[] { // Compute all distances const withDistances = points.map((p) => ({ id: p.id, distance: this.space.distance(query, p.point), point: p.point, metadata: p.metadata, })); // Sort by distance and take top k withDistances.sort((a, b) => a.distance - b.distance); return withDistances.slice(0, k); } /** * Computes the centroid of a set of points. * * @param points - Array of points * @param maxIter - Maximum iterations for iterative refinement * @returns Centroid point */ computeCentroid(points: number[][], maxIter: number = 100): number[] { return this.space.centroid(points, maxIter); } /** * Interpolates along geodesics between pairs of points. * * @param pairs - Array of [start, end] point pairs * @param t - Interpolation parameter (0 = start, 1 = end) * @returns Array of interpolated points */ batchGeodesic(pairs: [number[], number[]][], t: number): number[][] { return pairs.map(([a, b]) => this.space.geodesic(a, b, t)); } /** * Performs Mobius addition on pairs of points. * * @param pairs - Array of [a, b] point pairs * @returns Array of Mobius sums */ batchMobiusAdd(pairs: [number[], number[]][]): number[][] { return pairs.map(([a, b]) => this.space.mobiusAdd(a, b)); } } // ============================================================================ // Use Case Implementations // ============================================================================ /** * HierarchyEmbedder embeds tree-structured data in hyperbolic space. * * Useful for: * - Taxonomies (biological, product categories) * - Organizational charts * - File system hierarchies * - Knowledge graphs with hierarchical relations */ export class HierarchyEmbedder { private readonly space: HyperbolicSpace; private readonly dimension: number; constructor( dimension: number, model: HyperbolicModel = 'poincare', curvature: number = DEFAULT_CURVATURE ) { this.dimension = dimension; this.space = new HyperbolicSpace(model, curvature); } /** * Embeds a tree structure into hyperbolic space. * * Root is placed at the origin, children are placed along geodesics. * * @param tree - Tree structure with id, children, and optional data * @param angularSpread - Angular spread for children (default: 2*PI) * @returns Map of node IDs to embeddings */ embedTree<T extends { id: string; children?: T[]; data?: unknown }>( tree: T, angularSpread: number = 2 * Math.PI ): Map<string, number[]> { const embeddings = new Map<string, number[]>(); this.embedNode(tree, zeros(this.dimension), 0, angularSpread, embeddings); return embeddings; } private embedNode<T extends { id: string; children?: T[]; data?: unknown }>( node: T, position: number[], depth: number, angularSpread: number, embeddings: Map<string, number[]> ): void { embeddings.set(node.id, position); if (!node.children || node.children.length === 0) { return; } const numChildren = node.children.length; const angleStep = angularSpread / numChildren; const startAngle = -angularSpread / 2 + angleStep / 2; // Distance to children decreases with depth to fit more nodes const childDistance = 0.5 / (depth + 1); for (let i = 0; i < numChildren; i++) { const angle = startAngle + i * angleStep; // Create tangent vector in the direction of the angle const tangent = zeros(this.dimension); tangent[0] = childDistance * Math.cos(angle); if (this.dimension > 1) { tangent[1] = childDistance * Math.sin(angle); } // Map to child position using exponential map const childPos = this.space.expMap(position, tangent); // Recursively embed children with reduced angular spread this.embedNode( node.children[i], childPos, depth + 1, angularSpread / numChildren, embeddings ); } } /** * Gets the hyperbolic space instance for additional operations. */ getSpace(): HyperbolicSpace { return this.space; } } /** * Recursive tree node interface for embedding. */ export interface TreeNode { id: string; children?: TreeNode[]; data?: unknown; } /** * ASTEmbedder embeds Abstract Syntax Trees in hyperbolic space. * * Preserves the hierarchical structure of code, enabling: * - Similar code search * - Code clone detection * - Structural diff operations */ export class ASTEmbedder extends HierarchyEmbedder { /** * Embeds an AST node structure. * * @param ast - AST with type, children, and optional metadata * @returns Map of node paths to embeddings */ embedAST(ast: ASTNode): Map<string, number[]> { const treeNode = this.astToTree(ast, ''); return this.embedTree(treeNode); } privat