@sparkleideas/plugins 3.0.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +401 -0
- package/__tests__/collection-manager.test.ts +332 -0
- package/__tests__/dependency-graph.test.ts +434 -0
- package/__tests__/enhanced-plugin-registry.test.ts +488 -0
- package/__tests__/plugin-registry.test.ts +368 -0
- package/__tests__/ruvector-bridge.test.ts +2429 -0
- package/__tests__/ruvector-integration.test.ts +1602 -0
- package/__tests__/ruvector-migrations.test.ts +1099 -0
- package/__tests__/ruvector-quantization.test.ts +846 -0
- package/__tests__/ruvector-streaming.test.ts +1088 -0
- package/__tests__/sdk.test.ts +325 -0
- package/__tests__/security.test.ts +348 -0
- package/__tests__/utils/ruvector-test-utils.ts +860 -0
- package/examples/plugin-creator/index.ts +636 -0
- package/examples/plugin-creator/plugin-creator.test.ts +312 -0
- package/examples/ruvector/README.md +288 -0
- package/examples/ruvector/attention-patterns.ts +394 -0
- package/examples/ruvector/basic-usage.ts +288 -0
- package/examples/ruvector/docker-compose.yml +75 -0
- package/examples/ruvector/gnn-analysis.ts +501 -0
- package/examples/ruvector/hyperbolic-hierarchies.ts +557 -0
- package/examples/ruvector/init-db.sql +119 -0
- package/examples/ruvector/quantization.ts +680 -0
- package/examples/ruvector/self-learning.ts +447 -0
- package/examples/ruvector/semantic-search.ts +576 -0
- package/examples/ruvector/streaming-large-data.ts +507 -0
- package/examples/ruvector/transactions.ts +594 -0
- package/examples/ruvector-plugins/hook-pattern-library.ts +486 -0
- package/examples/ruvector-plugins/index.ts +79 -0
- package/examples/ruvector-plugins/intent-router.ts +354 -0
- package/examples/ruvector-plugins/mcp-tool-optimizer.ts +424 -0
- package/examples/ruvector-plugins/reasoning-bank.ts +657 -0
- package/examples/ruvector-plugins/ruvector-plugins.test.ts +518 -0
- package/examples/ruvector-plugins/semantic-code-search.ts +498 -0
- package/examples/ruvector-plugins/shared/index.ts +20 -0
- package/examples/ruvector-plugins/shared/vector-utils.ts +257 -0
- package/examples/ruvector-plugins/sona-learning.ts +445 -0
- package/package.json +97 -0
- package/src/collections/collection-manager.ts +661 -0
- package/src/collections/index.ts +56 -0
- package/src/collections/official/index.ts +1040 -0
- package/src/core/base-plugin.ts +416 -0
- package/src/core/plugin-interface.ts +215 -0
- package/src/hooks/index.ts +685 -0
- package/src/index.ts +378 -0
- package/src/integrations/agentic-flow.ts +743 -0
- package/src/integrations/index.ts +88 -0
- package/src/integrations/ruvector/ARCHITECTURE.md +1245 -0
- package/src/integrations/ruvector/attention-advanced.ts +1040 -0
- package/src/integrations/ruvector/attention-executor.ts +782 -0
- package/src/integrations/ruvector/attention-mechanisms.ts +757 -0
- package/src/integrations/ruvector/attention.ts +1063 -0
- package/src/integrations/ruvector/gnn.ts +3050 -0
- package/src/integrations/ruvector/hyperbolic.ts +1948 -0
- package/src/integrations/ruvector/index.ts +394 -0
- package/src/integrations/ruvector/migrations/001_create_extension.sql +135 -0
- package/src/integrations/ruvector/migrations/002_create_vector_tables.sql +259 -0
- package/src/integrations/ruvector/migrations/003_create_indices.sql +328 -0
- package/src/integrations/ruvector/migrations/004_create_functions.sql +598 -0
- package/src/integrations/ruvector/migrations/005_create_attention_functions.sql +654 -0
- package/src/integrations/ruvector/migrations/006_create_gnn_functions.sql +728 -0
- package/src/integrations/ruvector/migrations/007_create_hyperbolic_functions.sql +762 -0
- package/src/integrations/ruvector/migrations/index.ts +35 -0
- package/src/integrations/ruvector/migrations/migrations.ts +647 -0
- package/src/integrations/ruvector/quantization.ts +2036 -0
- package/src/integrations/ruvector/ruvector-bridge.ts +2000 -0
- package/src/integrations/ruvector/self-learning.ts +2376 -0
- package/src/integrations/ruvector/streaming.ts +1737 -0
- package/src/integrations/ruvector/types.ts +1945 -0
- package/src/providers/index.ts +643 -0
- package/src/registry/dependency-graph.ts +568 -0
- package/src/registry/enhanced-plugin-registry.ts +994 -0
- package/src/registry/plugin-registry.ts +604 -0
- package/src/sdk/index.ts +563 -0
- package/src/security/index.ts +594 -0
- package/src/types/index.ts +446 -0
- package/src/workers/index.ts +700 -0
- package/tmp.json +0 -0
- package/tsconfig.json +25 -0
- package/vitest.config.ts +23 -0
|
@@ -0,0 +1,1948 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RuVector PostgreSQL Bridge - Hyperbolic Embeddings Module
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive hyperbolic geometry support for embedding hierarchical data
|
|
5
|
+
* (taxonomies, org charts, ASTs, dependency graphs) in non-Euclidean spaces.
|
|
6
|
+
*
|
|
7
|
+
* Supports four hyperbolic models:
|
|
8
|
+
* - Poincare Ball Model: Conformal, good for visualization
|
|
9
|
+
* - Lorentz (Hyperboloid) Model: Numerically stable, good for optimization
|
|
10
|
+
* - Klein Model: Straight geodesics, good for convex optimization
|
|
11
|
+
* - Half-Space Model: Upper half-plane, good for theoretical analysis
|
|
12
|
+
*
|
|
13
|
+
* @module @sparkleideas/plugins/integrations/ruvector/hyperbolic
|
|
14
|
+
* @version 1.0.0
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type {
|
|
18
|
+
HyperbolicModel,
|
|
19
|
+
HyperbolicEmbedding,
|
|
20
|
+
HyperbolicOperation,
|
|
21
|
+
} from './types.js';
|
|
22
|
+
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Constants and Configuration
|
|
25
|
+
// ============================================================================
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Default numerical stability epsilon
|
|
29
|
+
*/
|
|
30
|
+
const DEFAULT_EPS = 1e-15;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Maximum norm for Poincare ball to maintain stability (must be < 1)
|
|
34
|
+
*/
|
|
35
|
+
const DEFAULT_MAX_NORM = 1 - 1e-5;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Default curvature for hyperbolic space (negative value)
|
|
39
|
+
*/
|
|
40
|
+
const DEFAULT_CURVATURE = -1.0;
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
// ============================================================================
|
|
44
|
+
// Utility Functions
|
|
45
|
+
// ============================================================================
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Computes the dot product of two vectors.
|
|
49
|
+
*/
|
|
50
|
+
function dot(a: number[], b: number[]): number {
|
|
51
|
+
let sum = 0;
|
|
52
|
+
for (let i = 0; i < a.length; i++) {
|
|
53
|
+
sum += a[i] * b[i];
|
|
54
|
+
}
|
|
55
|
+
return sum;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Computes the Euclidean (L2) norm of a vector.
|
|
60
|
+
*/
|
|
61
|
+
function norm(v: number[]): number {
|
|
62
|
+
return Math.sqrt(dot(v, v));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Computes the squared norm of a vector.
|
|
67
|
+
*/
|
|
68
|
+
function normSquared(v: number[]): number {
|
|
69
|
+
return dot(v, v);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Scales a vector by a scalar.
|
|
74
|
+
*/
|
|
75
|
+
function scale(v: number[], s: number): number[] {
|
|
76
|
+
return v.map((x) => x * s);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Adds two vectors.
|
|
81
|
+
*/
|
|
82
|
+
function add(a: number[], b: number[]): number[] {
|
|
83
|
+
return a.map((x, i) => x + b[i]);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Subtracts vector b from vector a.
|
|
88
|
+
*/
|
|
89
|
+
function sub(a: number[], b: number[]): number[] {
|
|
90
|
+
return a.map((x, i) => x - b[i]);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Clamps a value to a range.
|
|
95
|
+
*/
|
|
96
|
+
function clamp(value: number, min: number, max: number): number {
|
|
97
|
+
return Math.max(min, Math.min(max, value));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Safe arctanh implementation with clamping.
|
|
102
|
+
*/
|
|
103
|
+
function safeAtanh(x: number, eps: number = DEFAULT_EPS): number {
|
|
104
|
+
const clamped = clamp(x, -1 + eps, 1 - eps);
|
|
105
|
+
return 0.5 * Math.log((1 + clamped) / (1 - clamped));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Safe arccosh implementation.
|
|
110
|
+
*/
|
|
111
|
+
function safeAcosh(x: number, eps: number = DEFAULT_EPS): number {
|
|
112
|
+
return Math.acosh(Math.max(1 + eps, x));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Creates a zero vector of specified dimension.
|
|
117
|
+
*/
|
|
118
|
+
function zeros(dim: number): number[] {
|
|
119
|
+
return new Array(dim).fill(0);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ============================================================================
|
|
123
|
+
// Hyperbolic Space Configuration
|
|
124
|
+
// ============================================================================
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Configuration for a hyperbolic space instance.
|
|
128
|
+
*/
|
|
129
|
+
export interface HyperbolicSpaceConfig {
|
|
130
|
+
/** Hyperbolic model to use */
|
|
131
|
+
readonly model: HyperbolicModel;
|
|
132
|
+
/** Curvature parameter (negative for hyperbolic space) */
|
|
133
|
+
readonly curvature: number;
|
|
134
|
+
/** Embedding dimension */
|
|
135
|
+
readonly dimension: number;
|
|
136
|
+
/** Numerical stability epsilon */
|
|
137
|
+
readonly eps?: number;
|
|
138
|
+
/** Maximum norm for Poincare ball */
|
|
139
|
+
readonly maxNorm?: number;
|
|
140
|
+
/** Whether curvature is learnable */
|
|
141
|
+
readonly learnCurvature?: boolean;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Result from a hyperbolic distance computation.
|
|
146
|
+
*/
|
|
147
|
+
export interface HyperbolicDistanceResult {
|
|
148
|
+
/** Geodesic distance */
|
|
149
|
+
readonly distance: number;
|
|
150
|
+
/** Model used for computation */
|
|
151
|
+
readonly model: HyperbolicModel;
|
|
152
|
+
/** Effective curvature */
|
|
153
|
+
readonly curvature: number;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Result from a hyperbolic search operation.
|
|
158
|
+
*/
|
|
159
|
+
export interface HyperbolicSearchResult {
|
|
160
|
+
/** Point ID */
|
|
161
|
+
readonly id: string | number;
|
|
162
|
+
/** Geodesic distance from query */
|
|
163
|
+
readonly distance: number;
|
|
164
|
+
/** Point coordinates in hyperbolic space */
|
|
165
|
+
readonly point: number[];
|
|
166
|
+
/** Original metadata */
|
|
167
|
+
readonly metadata?: Record<string, unknown>;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Options for batch hyperbolic operations.
|
|
172
|
+
*/
|
|
173
|
+
export interface HyperbolicBatchOptions {
|
|
174
|
+
/** Points to process */
|
|
175
|
+
readonly points: number[][];
|
|
176
|
+
/** Operation to perform */
|
|
177
|
+
readonly operation: HyperbolicOperation;
|
|
178
|
+
/** Additional operation parameters */
|
|
179
|
+
readonly params?: {
|
|
180
|
+
readonly tangent?: number[];
|
|
181
|
+
readonly base?: number[];
|
|
182
|
+
readonly target?: number[];
|
|
183
|
+
readonly matrix?: number[][];
|
|
184
|
+
};
|
|
185
|
+
/** Process in parallel */
|
|
186
|
+
readonly parallel?: boolean;
|
|
187
|
+
/** Batch size for processing */
|
|
188
|
+
readonly batchSize?: number;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Result from batch hyperbolic operations.
|
|
193
|
+
*/
|
|
194
|
+
export interface HyperbolicBatchResult {
|
|
195
|
+
/** Resulting points/values */
|
|
196
|
+
readonly results: number[][];
|
|
197
|
+
/** Operation performed */
|
|
198
|
+
readonly operation: HyperbolicOperation;
|
|
199
|
+
/** Processing time in milliseconds */
|
|
200
|
+
readonly durationMs: number;
|
|
201
|
+
/** Number of points processed */
|
|
202
|
+
readonly count: number;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ============================================================================
|
|
206
|
+
// HyperbolicSpace Class
|
|
207
|
+
// ============================================================================
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* HyperbolicSpace provides comprehensive operations for hyperbolic geometry.
|
|
211
|
+
*
|
|
212
|
+
* Supports Poincare ball, Lorentz (hyperboloid), Klein disk, and half-space models.
|
|
213
|
+
* All operations are numerically stable and handle edge cases gracefully.
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* ```typescript
|
|
217
|
+
* const space = new HyperbolicSpace('poincare', -1.0);
|
|
218
|
+
* const dist = space.distance([0.1, 0.2], [0.3, 0.4]);
|
|
219
|
+
* const mapped = space.expMap([0, 0], [0.1, 0.2]);
|
|
220
|
+
* ```
|
|
221
|
+
*/
|
|
222
|
+
export class HyperbolicSpace {
|
|
223
|
+
/** Current hyperbolic model */
|
|
224
|
+
readonly model: HyperbolicModel;
|
|
225
|
+
/** Curvature parameter (negative for hyperbolic) */
|
|
226
|
+
private _curvature: number;
|
|
227
|
+
/** Numerical stability epsilon */
|
|
228
|
+
readonly eps: number;
|
|
229
|
+
/** Maximum norm for Poincare ball */
|
|
230
|
+
readonly maxNorm: number;
|
|
231
|
+
/** Scaling factor derived from curvature: sqrt(|c|) */
|
|
232
|
+
private _scale: number;
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Creates a new HyperbolicSpace instance.
|
|
236
|
+
*
|
|
237
|
+
* @param model - Hyperbolic model to use
|
|
238
|
+
* @param curvature - Curvature parameter (should be negative, will be negated if positive)
|
|
239
|
+
* @param eps - Numerical stability epsilon
|
|
240
|
+
* @param maxNorm - Maximum norm for Poincare ball
|
|
241
|
+
*/
|
|
242
|
+
constructor(
|
|
243
|
+
model: HyperbolicModel,
|
|
244
|
+
curvature: number = DEFAULT_CURVATURE,
|
|
245
|
+
eps: number = DEFAULT_EPS,
|
|
246
|
+
maxNorm: number = DEFAULT_MAX_NORM
|
|
247
|
+
) {
|
|
248
|
+
this.model = model;
|
|
249
|
+
this._curvature = curvature > 0 ? -curvature : curvature;
|
|
250
|
+
this.eps = eps;
|
|
251
|
+
this.maxNorm = maxNorm;
|
|
252
|
+
this._scale = Math.sqrt(Math.abs(this._curvature));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Gets the current curvature.
|
|
257
|
+
*/
|
|
258
|
+
get curvature(): number {
|
|
259
|
+
return this._curvature;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Gets the scaling factor sqrt(|c|).
|
|
264
|
+
*/
|
|
265
|
+
get scale(): number {
|
|
266
|
+
return this._scale;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Updates the curvature (for learnable curvature scenarios).
|
|
271
|
+
*/
|
|
272
|
+
setCurvature(c: number): void {
|
|
273
|
+
this._curvature = c > 0 ? -c : c;
|
|
274
|
+
this._scale = Math.sqrt(Math.abs(this._curvature));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ==========================================================================
|
|
278
|
+
// Distance Calculations
|
|
279
|
+
// ==========================================================================
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Computes the geodesic distance between two points in hyperbolic space.
|
|
283
|
+
*
|
|
284
|
+
* The distance formula depends on the model:
|
|
285
|
+
* - Poincare: d(u,v) = (2/sqrt|c|) * arctanh(sqrt|c| * ||(-u) + v||_M)
|
|
286
|
+
* - Lorentz: d(u,v) = (1/sqrt|c|) * arcosh(-c * <u,v>_L)
|
|
287
|
+
* - Klein: Converted to Poincare first
|
|
288
|
+
* - Half-space: d(u,v) = arcosh(1 + ||u-v||^2 / (2*u_n*v_n))
|
|
289
|
+
*
|
|
290
|
+
* @param a - First point
|
|
291
|
+
* @param b - Second point
|
|
292
|
+
* @returns Geodesic distance
|
|
293
|
+
*/
|
|
294
|
+
distance(a: number[], b: number[]): number {
|
|
295
|
+
switch (this.model) {
|
|
296
|
+
case 'poincare':
|
|
297
|
+
return this.poincareDistance(a, b);
|
|
298
|
+
case 'lorentz':
|
|
299
|
+
return this.lorentzDistance(a, b);
|
|
300
|
+
case 'klein':
|
|
301
|
+
return this.kleinDistance(a, b);
|
|
302
|
+
case 'half_space':
|
|
303
|
+
return this.halfSpaceDistance(a, b);
|
|
304
|
+
default:
|
|
305
|
+
throw new Error(`Unknown hyperbolic model: ${this.model}`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Computes distance in the Poincare ball model.
|
|
311
|
+
*
|
|
312
|
+
* Formula: d(u,v) = (2/sqrt|c|) * arctanh(sqrt|c| * ||(-u) +_M v||)
|
|
313
|
+
*
|
|
314
|
+
* Where +_M is Mobius addition.
|
|
315
|
+
*/
|
|
316
|
+
private poincareDistance(u: number[], v: number[]): number {
|
|
317
|
+
// Use Mobius addition: -u +_M v
|
|
318
|
+
const negU = scale(u, -1);
|
|
319
|
+
const diff = this.mobiusAdd(negU, v);
|
|
320
|
+
const diffNorm = norm(diff);
|
|
321
|
+
|
|
322
|
+
// d = (2/sqrt|c|) * arctanh(sqrt|c| * ||diff||)
|
|
323
|
+
const scaledNorm = this._scale * diffNorm;
|
|
324
|
+
return (2 / this._scale) * safeAtanh(scaledNorm, this.eps);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Computes distance in the Lorentz (hyperboloid) model.
|
|
329
|
+
*
|
|
330
|
+
* Formula: d(u,v) = (1/sqrt|c|) * arcosh(-c * <u,v>_L)
|
|
331
|
+
*
|
|
332
|
+
* Where <u,v>_L is the Lorentz inner product: -u0*v0 + u1*v1 + ... + un*vn
|
|
333
|
+
*/
|
|
334
|
+
private lorentzDistance(u: number[], v: number[]): number {
|
|
335
|
+
const lorentzInner = this.lorentzInnerProduct(u, v);
|
|
336
|
+
// -c * <u,v>_L for negative curvature
|
|
337
|
+
const argument = -this._curvature * lorentzInner;
|
|
338
|
+
return safeAcosh(argument, this.eps) / this._scale;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Computes distance in the Klein model.
|
|
343
|
+
* Converts to Poincare first for numerical stability.
|
|
344
|
+
*/
|
|
345
|
+
private kleinDistance(u: number[], v: number[]): number {
|
|
346
|
+
const uPoincare = this.kleinToPoincare(u);
|
|
347
|
+
const vPoincare = this.kleinToPoincare(v);
|
|
348
|
+
return this.poincareDistance(uPoincare, vPoincare);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Computes distance in the half-space model.
|
|
353
|
+
*
|
|
354
|
+
* Formula: d(u,v) = arcosh(1 + ||u-v||^2 / (2*u_n*v_n))
|
|
355
|
+
*
|
|
356
|
+
* Where u_n, v_n are the last coordinates (must be positive).
|
|
357
|
+
*/
|
|
358
|
+
private halfSpaceDistance(u: number[], v: number[]): number {
|
|
359
|
+
const n = u.length - 1;
|
|
360
|
+
const un = Math.max(u[n], this.eps);
|
|
361
|
+
const vn = Math.max(v[n], this.eps);
|
|
362
|
+
|
|
363
|
+
const diffSq = normSquared(sub(u, v));
|
|
364
|
+
const argument = 1 + diffSq / (2 * un * vn);
|
|
365
|
+
|
|
366
|
+
return safeAcosh(argument, this.eps) / this._scale;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Computes the Lorentz inner product.
|
|
371
|
+
* <u,v>_L = -u0*v0 + sum(u_i*v_i for i=1..n)
|
|
372
|
+
*/
|
|
373
|
+
private lorentzInnerProduct(u: number[], v: number[]): number {
|
|
374
|
+
let result = -u[0] * v[0]; // Time component (negative)
|
|
375
|
+
for (let i = 1; i < u.length; i++) {
|
|
376
|
+
result += u[i] * v[i]; // Spatial components (positive)
|
|
377
|
+
}
|
|
378
|
+
return result;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// ==========================================================================
|
|
382
|
+
// Exponential and Logarithmic Maps
|
|
383
|
+
// ==========================================================================
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Exponential map: Maps a tangent vector at a base point to the manifold.
|
|
387
|
+
*
|
|
388
|
+
* exp_p(v) moves from point p in the direction of tangent vector v.
|
|
389
|
+
*
|
|
390
|
+
* @param base - Base point on the manifold
|
|
391
|
+
* @param tangent - Tangent vector at the base point
|
|
392
|
+
* @returns Point on the manifold
|
|
393
|
+
*/
|
|
394
|
+
expMap(base: number[], tangent: number[]): number[] {
|
|
395
|
+
switch (this.model) {
|
|
396
|
+
case 'poincare':
|
|
397
|
+
return this.poincareExpMap(base, tangent);
|
|
398
|
+
case 'lorentz':
|
|
399
|
+
return this.lorentzExpMap(base, tangent);
|
|
400
|
+
case 'klein':
|
|
401
|
+
return this.kleinExpMap(base, tangent);
|
|
402
|
+
case 'half_space':
|
|
403
|
+
return this.halfSpaceExpMap(base, tangent);
|
|
404
|
+
default:
|
|
405
|
+
throw new Error(`Unknown hyperbolic model: ${this.model}`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Logarithmic map: Maps a point on the manifold to a tangent vector at base.
|
|
411
|
+
*
|
|
412
|
+
* log_p(q) gives the tangent vector at p pointing towards q.
|
|
413
|
+
*
|
|
414
|
+
* @param base - Base point on the manifold
|
|
415
|
+
* @param point - Target point on the manifold
|
|
416
|
+
* @returns Tangent vector at the base point
|
|
417
|
+
*/
|
|
418
|
+
logMap(base: number[], point: number[]): number[] {
|
|
419
|
+
switch (this.model) {
|
|
420
|
+
case 'poincare':
|
|
421
|
+
return this.poincareLogMap(base, point);
|
|
422
|
+
case 'lorentz':
|
|
423
|
+
return this.lorentzLogMap(base, point);
|
|
424
|
+
case 'klein':
|
|
425
|
+
return this.kleinLogMap(base, point);
|
|
426
|
+
case 'half_space':
|
|
427
|
+
return this.halfSpaceLogMap(base, point);
|
|
428
|
+
default:
|
|
429
|
+
throw new Error(`Unknown hyperbolic model: ${this.model}`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Poincare exponential map.
|
|
435
|
+
*
|
|
436
|
+
* exp_p(v) = p +_M (tanh(sqrt|c| * ||v||_p / 2) * v / (sqrt|c| * ||v||_p))
|
|
437
|
+
*
|
|
438
|
+
* Where ||v||_p is the Poincare tangent norm: lambda_p * ||v||
|
|
439
|
+
* And lambda_p = 2 / (1 - |c| * ||p||^2) is the conformal factor.
|
|
440
|
+
*/
|
|
441
|
+
private poincareExpMap(base: number[], tangent: number[]): number[] {
|
|
442
|
+
const tangentNorm = norm(tangent);
|
|
443
|
+
if (tangentNorm < this.eps) {
|
|
444
|
+
return [...base];
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Conformal factor at base
|
|
448
|
+
const baseSq = normSquared(base);
|
|
449
|
+
const lambda = 2 / (1 - Math.abs(this._curvature) * baseSq);
|
|
450
|
+
|
|
451
|
+
// Scaled tangent norm
|
|
452
|
+
const vNorm = lambda * tangentNorm;
|
|
453
|
+
|
|
454
|
+
// Compute direction and scale
|
|
455
|
+
const t = Math.tanh(this._scale * vNorm / 2);
|
|
456
|
+
const direction = scale(tangent, t / (this._scale * vNorm));
|
|
457
|
+
|
|
458
|
+
// Mobius add base + direction
|
|
459
|
+
return this.projectToManifold(this.mobiusAdd(base, direction));
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Poincare logarithmic map.
|
|
464
|
+
*
|
|
465
|
+
* log_p(q) = (2 / (sqrt|c| * lambda_p)) * arctanh(sqrt|c| * ||(-p) +_M q||) * ((-p) +_M q) / ||(-p) +_M q||
|
|
466
|
+
*/
|
|
467
|
+
private poincareLogMap(base: number[], point: number[]): number[] {
|
|
468
|
+
const negBase = scale(base, -1);
|
|
469
|
+
const diff = this.mobiusAdd(negBase, point);
|
|
470
|
+
const diffNorm = norm(diff);
|
|
471
|
+
|
|
472
|
+
if (diffNorm < this.eps) {
|
|
473
|
+
return zeros(base.length);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Conformal factor at base
|
|
477
|
+
const baseSq = normSquared(base);
|
|
478
|
+
const lambda = 2 / (1 - Math.abs(this._curvature) * baseSq);
|
|
479
|
+
|
|
480
|
+
// Compute coefficient
|
|
481
|
+
const atanh_arg = this._scale * diffNorm;
|
|
482
|
+
const coeff = (2 / (this._scale * lambda)) * safeAtanh(atanh_arg, this.eps);
|
|
483
|
+
|
|
484
|
+
// Direction
|
|
485
|
+
return scale(diff, coeff / diffNorm);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Lorentz exponential map.
|
|
490
|
+
*
|
|
491
|
+
* exp_p(v) = cosh(||v||_L) * p + sinh(||v||_L) * v / ||v||_L
|
|
492
|
+
*
|
|
493
|
+
* Where ||v||_L is the Lorentz norm: sqrt(<v,v>_L)
|
|
494
|
+
*/
|
|
495
|
+
private lorentzExpMap(base: number[], tangent: number[]): number[] {
|
|
496
|
+
const tangentNormSq = this.lorentzInnerProduct(tangent, tangent);
|
|
497
|
+
|
|
498
|
+
if (tangentNormSq < this.eps * this.eps) {
|
|
499
|
+
return [...base];
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const tangentNorm = Math.sqrt(Math.max(0, tangentNormSq));
|
|
503
|
+
const scaledNorm = this._scale * tangentNorm;
|
|
504
|
+
|
|
505
|
+
const coshVal = Math.cosh(scaledNorm);
|
|
506
|
+
const sinhVal = Math.sinh(scaledNorm);
|
|
507
|
+
|
|
508
|
+
const result = add(
|
|
509
|
+
scale(base, coshVal),
|
|
510
|
+
scale(tangent, sinhVal / tangentNorm)
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
return this.projectToManifold(result);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Lorentz logarithmic map.
|
|
518
|
+
*
|
|
519
|
+
* log_p(q) = (arcosh(-<p,q>_L) / sqrt(-<p,q>_L^2 - 1)) * (q + <p,q>_L * p)
|
|
520
|
+
*/
|
|
521
|
+
private lorentzLogMap(base: number[], point: number[]): number[] {
|
|
522
|
+
const inner = this.lorentzInnerProduct(base, point);
|
|
523
|
+
const alpha = -inner;
|
|
524
|
+
|
|
525
|
+
if (alpha <= 1 + this.eps) {
|
|
526
|
+
return zeros(base.length);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const sqrtArg = Math.sqrt(alpha * alpha - 1);
|
|
530
|
+
const coeff = safeAcosh(alpha, this.eps) / sqrtArg;
|
|
531
|
+
|
|
532
|
+
// v = q + <p,q>_L * p, but we need q - alpha * p for tangent
|
|
533
|
+
const tangent = sub(point, scale(base, alpha));
|
|
534
|
+
|
|
535
|
+
return scale(tangent, coeff);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Klein exponential map (via Poincare).
|
|
540
|
+
*/
|
|
541
|
+
private kleinExpMap(base: number[], tangent: number[]): number[] {
|
|
542
|
+
const basePoincare = this.kleinToPoincare(base);
|
|
543
|
+
const tangentPoincare = this.kleinTangentToPoincare(base, tangent);
|
|
544
|
+
const resultPoincare = this.poincareExpMap(basePoincare, tangentPoincare);
|
|
545
|
+
return this.poincareToKlein(resultPoincare);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Klein logarithmic map (via Poincare).
|
|
550
|
+
*/
|
|
551
|
+
private kleinLogMap(base: number[], point: number[]): number[] {
|
|
552
|
+
const basePoincare = this.kleinToPoincare(base);
|
|
553
|
+
const pointPoincare = this.kleinToPoincare(point);
|
|
554
|
+
const tangentPoincare = this.poincareLogMap(basePoincare, pointPoincare);
|
|
555
|
+
return this.poincareTangentToKlein(base, tangentPoincare);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Half-space exponential map.
|
|
560
|
+
*/
|
|
561
|
+
private halfSpaceExpMap(base: number[], tangent: number[]): number[] {
|
|
562
|
+
// For half-space, use the Riemannian metric g = (1/x_n^2) * I
|
|
563
|
+
const n = base.length - 1;
|
|
564
|
+
const xn = Math.max(base[n], this.eps);
|
|
565
|
+
|
|
566
|
+
// Scale tangent by conformal factor
|
|
567
|
+
const scaledTangent = scale(tangent, xn);
|
|
568
|
+
const tangentNorm = norm(scaledTangent);
|
|
569
|
+
|
|
570
|
+
if (tangentNorm < this.eps) {
|
|
571
|
+
return [...base];
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Geodesic in half-space model
|
|
575
|
+
const t = tangentNorm * this._scale;
|
|
576
|
+
const direction = scale(scaledTangent, 1 / tangentNorm);
|
|
577
|
+
|
|
578
|
+
const result = add(base, scale(direction, Math.sinh(t) * xn / this._scale));
|
|
579
|
+
result[n] = xn * Math.cosh(t);
|
|
580
|
+
|
|
581
|
+
return this.projectToManifold(result);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Half-space logarithmic map.
|
|
586
|
+
*/
|
|
587
|
+
private halfSpaceLogMap(base: number[], point: number[]): number[] {
|
|
588
|
+
const n = base.length - 1;
|
|
589
|
+
const xn = Math.max(base[n], this.eps);
|
|
590
|
+
const yn = Math.max(point[n], this.eps);
|
|
591
|
+
|
|
592
|
+
const diff = sub(point, base);
|
|
593
|
+
const diffSq = normSquared(diff);
|
|
594
|
+
|
|
595
|
+
const argument = 1 + diffSq / (2 * xn * yn);
|
|
596
|
+
const dist = safeAcosh(argument, this.eps);
|
|
597
|
+
|
|
598
|
+
if (dist < this.eps) {
|
|
599
|
+
return zeros(base.length);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Compute initial tangent direction
|
|
603
|
+
const direction = scale(diff, 1 / Math.sqrt(diffSq + this.eps));
|
|
604
|
+
return scale(direction, dist * xn / this._scale);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// ==========================================================================
|
|
608
|
+
// Mobius Operations (Poincare Ball)
|
|
609
|
+
// ==========================================================================
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Mobius addition in the Poincare ball.
|
|
613
|
+
*
|
|
614
|
+
* 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)
|
|
615
|
+
*
|
|
616
|
+
* @param a - First point
|
|
617
|
+
* @param b - Second point
|
|
618
|
+
* @returns Mobius sum
|
|
619
|
+
*/
|
|
620
|
+
mobiusAdd(a: number[], b: number[]): number[] {
|
|
621
|
+
const c = Math.abs(this._curvature);
|
|
622
|
+
const aSq = normSquared(a);
|
|
623
|
+
const bSq = normSquared(b);
|
|
624
|
+
const ab = dot(a, b);
|
|
625
|
+
|
|
626
|
+
const numerator1 = 1 + 2 * c * ab + c * bSq;
|
|
627
|
+
const numerator2 = 1 - c * aSq;
|
|
628
|
+
const denominator = 1 + 2 * c * ab + c * c * aSq * bSq;
|
|
629
|
+
|
|
630
|
+
const safeD = Math.max(denominator, this.eps);
|
|
631
|
+
const result = add(
|
|
632
|
+
scale(a, numerator1 / safeD),
|
|
633
|
+
scale(b, numerator2 / safeD)
|
|
634
|
+
);
|
|
635
|
+
|
|
636
|
+
return this.projectToManifold(result);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Mobius matrix-vector multiplication.
|
|
641
|
+
*
|
|
642
|
+
* M otimes_M v = tanh(||Mv|| / ||v|| * arctanh(||v||)) * Mv / ||Mv||
|
|
643
|
+
*
|
|
644
|
+
* This applies a matrix transformation in hyperbolic space.
|
|
645
|
+
*
|
|
646
|
+
* @param matrix - Transformation matrix
|
|
647
|
+
* @param vec - Vector in Poincare ball
|
|
648
|
+
* @returns Transformed vector
|
|
649
|
+
*/
|
|
650
|
+
mobiusMatVec(matrix: number[][], vec: number[]): number[] {
|
|
651
|
+
// First, compute Mv in Euclidean space
|
|
652
|
+
const mv: number[] = [];
|
|
653
|
+
for (let i = 0; i < matrix.length; i++) {
|
|
654
|
+
let sum = 0;
|
|
655
|
+
for (let j = 0; j < vec.length; j++) {
|
|
656
|
+
sum += matrix[i][j] * vec[j];
|
|
657
|
+
}
|
|
658
|
+
mv.push(sum);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const mvNorm = norm(mv);
|
|
662
|
+
const vNorm = norm(vec);
|
|
663
|
+
|
|
664
|
+
if (vNorm < this.eps || mvNorm < this.eps) {
|
|
665
|
+
return this.projectToManifold(mv);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// Apply hyperbolic scaling
|
|
669
|
+
const scaledVNorm = this._scale * vNorm;
|
|
670
|
+
const atanhV = safeAtanh(scaledVNorm, this.eps);
|
|
671
|
+
const scaleFactor = Math.tanh(mvNorm / vNorm * atanhV) / (this._scale * mvNorm);
|
|
672
|
+
|
|
673
|
+
return this.projectToManifold(scale(mv, scaleFactor));
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Mobius scalar multiplication.
|
|
678
|
+
*
|
|
679
|
+
* r *_M x = tanh(r * arctanh(sqrt|c| * ||x||)) * x / (sqrt|c| * ||x||)
|
|
680
|
+
*
|
|
681
|
+
* @param r - Scalar multiplier
|
|
682
|
+
* @param x - Point in Poincare ball
|
|
683
|
+
* @returns Scaled point
|
|
684
|
+
*/
|
|
685
|
+
mobiusScalarMul(r: number, x: number[]): number[] {
|
|
686
|
+
const xNorm = norm(x);
|
|
687
|
+
if (xNorm < this.eps) {
|
|
688
|
+
return zeros(x.length);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
const scaledNorm = this._scale * xNorm;
|
|
692
|
+
const atanhX = safeAtanh(scaledNorm, this.eps);
|
|
693
|
+
const newNorm = Math.tanh(r * atanhX) / this._scale;
|
|
694
|
+
|
|
695
|
+
return this.projectToManifold(scale(x, newNorm / xNorm));
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// ==========================================================================
|
|
699
|
+
// Projection Operations
|
|
700
|
+
// ==========================================================================
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* Projects a point onto the hyperbolic manifold.
|
|
704
|
+
*
|
|
705
|
+
* For Poincare: Ensures ||x|| < maxNorm
|
|
706
|
+
* For Lorentz: Ensures x is on the hyperboloid
|
|
707
|
+
* For Klein: Ensures ||x|| < 1
|
|
708
|
+
* For Half-space: Ensures last coordinate > eps
|
|
709
|
+
*
|
|
710
|
+
* @param point - Point to project
|
|
711
|
+
* @returns Point on the manifold
|
|
712
|
+
*/
|
|
713
|
+
projectToManifold(point: number[]): number[] {
|
|
714
|
+
switch (this.model) {
|
|
715
|
+
case 'poincare':
|
|
716
|
+
return this.projectToPoincare(point);
|
|
717
|
+
case 'lorentz':
|
|
718
|
+
return this.projectToLorentz(point);
|
|
719
|
+
case 'klein':
|
|
720
|
+
return this.projectToKlein(point);
|
|
721
|
+
case 'half_space':
|
|
722
|
+
return this.projectToHalfSpace(point);
|
|
723
|
+
default:
|
|
724
|
+
throw new Error(`Unknown hyperbolic model: ${this.model}`);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Projects onto the Poincare ball (||x|| < maxNorm).
|
|
730
|
+
*/
|
|
731
|
+
private projectToPoincare(point: number[]): number[] {
|
|
732
|
+
const n = norm(point);
|
|
733
|
+
if (n >= this.maxNorm) {
|
|
734
|
+
return scale(point, (this.maxNorm - this.eps) / n);
|
|
735
|
+
}
|
|
736
|
+
return point;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Projects onto the Lorentz hyperboloid.
|
|
741
|
+
* Ensures <x,x>_L = -1/c
|
|
742
|
+
*/
|
|
743
|
+
private projectToLorentz(point: number[]): number[] {
|
|
744
|
+
// Compute spatial norm
|
|
745
|
+
let spatialSq = 0;
|
|
746
|
+
for (let i = 1; i < point.length; i++) {
|
|
747
|
+
spatialSq += point[i] * point[i];
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Time component: x0 = sqrt(1/|c| + spatial^2)
|
|
751
|
+
const x0 = Math.sqrt(1 / Math.abs(this._curvature) + spatialSq);
|
|
752
|
+
|
|
753
|
+
const result = [...point];
|
|
754
|
+
result[0] = x0;
|
|
755
|
+
return result;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Projects onto the Klein disk (||x|| < 1).
|
|
760
|
+
*/
|
|
761
|
+
private projectToKlein(point: number[]): number[] {
|
|
762
|
+
const n = norm(point);
|
|
763
|
+
if (n >= 1 - this.eps) {
|
|
764
|
+
return scale(point, (1 - 2 * this.eps) / n);
|
|
765
|
+
}
|
|
766
|
+
return point;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* Projects onto the half-space (last coordinate > eps).
|
|
771
|
+
*/
|
|
772
|
+
private projectToHalfSpace(point: number[]): number[] {
|
|
773
|
+
const result = [...point];
|
|
774
|
+
const lastIdx = point.length - 1;
|
|
775
|
+
result[lastIdx] = Math.max(result[lastIdx], this.eps);
|
|
776
|
+
return result;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* Projects a vector onto the tangent space at a given base point.
|
|
781
|
+
*
|
|
782
|
+
* @param base - Base point on the manifold
|
|
783
|
+
* @param vec - Vector to project
|
|
784
|
+
* @returns Vector in the tangent space
|
|
785
|
+
*/
|
|
786
|
+
projectToTangent(base: number[], vec: number[]): number[] {
|
|
787
|
+
switch (this.model) {
|
|
788
|
+
case 'poincare':
|
|
789
|
+
// In Poincare ball, tangent space is R^n (no projection needed for vectors)
|
|
790
|
+
return vec;
|
|
791
|
+
case 'lorentz':
|
|
792
|
+
return this.projectToLorentzTangent(base, vec);
|
|
793
|
+
case 'klein':
|
|
794
|
+
return vec;
|
|
795
|
+
case 'half_space':
|
|
796
|
+
return vec;
|
|
797
|
+
default:
|
|
798
|
+
throw new Error(`Unknown hyperbolic model: ${this.model}`);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Projects onto the Lorentz tangent space.
|
|
804
|
+
* Tangent vectors must satisfy <p, v>_L = 0.
|
|
805
|
+
*/
|
|
806
|
+
private projectToLorentzTangent(base: number[], vec: number[]): number[] {
|
|
807
|
+
const inner = this.lorentzInnerProduct(base, vec);
|
|
808
|
+
const baseInner = this.lorentzInnerProduct(base, base);
|
|
809
|
+
const coeff = inner / Math.min(baseInner, -this.eps);
|
|
810
|
+
|
|
811
|
+
return sub(vec, scale(base, coeff));
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// ==========================================================================
|
|
815
|
+
// Model Conversions
|
|
816
|
+
// ==========================================================================
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* Converts a point from Poincare ball to Lorentz hyperboloid.
|
|
820
|
+
*
|
|
821
|
+
* Lorentz: (x0, x1, ..., xn) where x0 is the time component
|
|
822
|
+
* x0 = (1 + |c| * ||p||^2) / (1 - |c| * ||p||^2)
|
|
823
|
+
* xi = 2 * sqrt|c| * pi / (1 - |c| * ||p||^2)
|
|
824
|
+
*
|
|
825
|
+
* @param poincare - Point in Poincare ball
|
|
826
|
+
* @returns Point on Lorentz hyperboloid
|
|
827
|
+
*/
|
|
828
|
+
toLorentz(poincare: number[]): number[] {
|
|
829
|
+
const c = Math.abs(this._curvature);
|
|
830
|
+
const pSq = normSquared(poincare);
|
|
831
|
+
const denom = Math.max(1 - c * pSq, this.eps);
|
|
832
|
+
|
|
833
|
+
const x0 = (1 + c * pSq) / denom;
|
|
834
|
+
const lorentz = [x0];
|
|
835
|
+
|
|
836
|
+
for (let i = 0; i < poincare.length; i++) {
|
|
837
|
+
lorentz.push((2 * this._scale * poincare[i]) / denom);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
return lorentz;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* Converts a point from Lorentz hyperboloid to Poincare ball.
|
|
845
|
+
*
|
|
846
|
+
* pi = xi / (sqrt|c| * (x0 + 1))
|
|
847
|
+
*
|
|
848
|
+
* @param lorentz - Point on Lorentz hyperboloid
|
|
849
|
+
* @returns Point in Poincare ball
|
|
850
|
+
*/
|
|
851
|
+
toPoincare(lorentz: number[]): number[] {
|
|
852
|
+
const denom = this._scale * (lorentz[0] + 1);
|
|
853
|
+
const poincare: number[] = [];
|
|
854
|
+
|
|
855
|
+
for (let i = 1; i < lorentz.length; i++) {
|
|
856
|
+
poincare.push(lorentz[i] / Math.max(denom, this.eps));
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
return this.projectToPoincare(poincare);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* Converts a point from Klein disk to Poincare ball.
|
|
864
|
+
*
|
|
865
|
+
* pi = ki / (1 + sqrt(1 - |c| * ||k||^2))
|
|
866
|
+
*
|
|
867
|
+
* @param klein - Point in Klein disk
|
|
868
|
+
* @returns Point in Poincare ball
|
|
869
|
+
*/
|
|
870
|
+
kleinToPoincare(klein: number[]): number[] {
|
|
871
|
+
const c = Math.abs(this._curvature);
|
|
872
|
+
const kSq = normSquared(klein);
|
|
873
|
+
const sqrtArg = Math.sqrt(Math.max(1 - c * kSq, this.eps));
|
|
874
|
+
const denom = 1 + sqrtArg;
|
|
875
|
+
|
|
876
|
+
return this.projectToPoincare(scale(klein, 1 / denom));
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* Converts a point from Poincare ball to Klein disk.
|
|
881
|
+
*
|
|
882
|
+
* ki = 2 * pi / (1 + |c| * ||p||^2)
|
|
883
|
+
*
|
|
884
|
+
* @param poincare - Point in Poincare ball
|
|
885
|
+
* @returns Point in Klein disk
|
|
886
|
+
*/
|
|
887
|
+
poincareToKlein(poincare: number[]): number[] {
|
|
888
|
+
const c = Math.abs(this._curvature);
|
|
889
|
+
const pSq = normSquared(poincare);
|
|
890
|
+
const factor = 2 / (1 + c * pSq);
|
|
891
|
+
|
|
892
|
+
return this.projectToKlein(scale(poincare, factor));
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
/**
|
|
896
|
+
* Converts a tangent vector from Klein to Poincare.
|
|
897
|
+
*/
|
|
898
|
+
private kleinTangentToPoincare(kleinBase: number[], kleinTangent: number[]): number[] {
|
|
899
|
+
const c = Math.abs(this._curvature);
|
|
900
|
+
const kSq = normSquared(kleinBase);
|
|
901
|
+
const sqrtArg = Math.sqrt(Math.max(1 - c * kSq, this.eps));
|
|
902
|
+
const factor = sqrtArg / (1 + sqrtArg);
|
|
903
|
+
|
|
904
|
+
return scale(kleinTangent, factor);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/**
|
|
908
|
+
* Converts a tangent vector from Poincare to Klein.
|
|
909
|
+
*/
|
|
910
|
+
private poincareTangentToKlein(kleinBase: number[], poincareTangent: number[]): number[] {
|
|
911
|
+
const c = Math.abs(this._curvature);
|
|
912
|
+
const kSq = normSquared(kleinBase);
|
|
913
|
+
const sqrtArg = Math.sqrt(Math.max(1 - c * kSq, this.eps));
|
|
914
|
+
const factor = (1 + sqrtArg) / sqrtArg;
|
|
915
|
+
|
|
916
|
+
return scale(poincareTangent, factor);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* Converts a point from Poincare ball to half-space model.
|
|
921
|
+
*
|
|
922
|
+
* @param poincare - Point in Poincare ball
|
|
923
|
+
* @returns Point in half-space model
|
|
924
|
+
*/
|
|
925
|
+
poincareToHalfSpace(poincare: number[]): number[] {
|
|
926
|
+
const c = Math.abs(this._curvature);
|
|
927
|
+
const n = poincare.length;
|
|
928
|
+
const pSq = normSquared(poincare);
|
|
929
|
+
const pn = poincare[n - 1];
|
|
930
|
+
|
|
931
|
+
const denom = pSq + 2 * pn / this._scale + 1 / c;
|
|
932
|
+
const safeDenom = Math.max(Math.abs(denom), this.eps) * Math.sign(denom || 1);
|
|
933
|
+
|
|
934
|
+
const result: number[] = [];
|
|
935
|
+
for (let i = 0; i < n - 1; i++) {
|
|
936
|
+
result.push(poincare[i] / safeDenom);
|
|
937
|
+
}
|
|
938
|
+
result.push((1 - c * pSq) / (2 * this._scale * safeDenom));
|
|
939
|
+
|
|
940
|
+
return this.projectToHalfSpace(result);
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/**
|
|
944
|
+
* Converts a point from half-space model to Poincare ball.
|
|
945
|
+
*
|
|
946
|
+
* @param halfSpace - Point in half-space model
|
|
947
|
+
* @returns Point in Poincare ball
|
|
948
|
+
*/
|
|
949
|
+
halfSpaceToPoincare(halfSpace: number[]): number[] {
|
|
950
|
+
const n = halfSpace.length;
|
|
951
|
+
const xn = Math.max(halfSpace[n - 1], this.eps);
|
|
952
|
+
|
|
953
|
+
let xSq = 0;
|
|
954
|
+
for (let i = 0; i < n - 1; i++) {
|
|
955
|
+
xSq += halfSpace[i] * halfSpace[i];
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
const denom = xSq + (xn + 1 / this._scale) * (xn + 1 / this._scale);
|
|
959
|
+
const safeDenom = Math.max(denom, this.eps);
|
|
960
|
+
|
|
961
|
+
const result: number[] = [];
|
|
962
|
+
for (let i = 0; i < n - 1; i++) {
|
|
963
|
+
result.push((2 * halfSpace[i]) / safeDenom);
|
|
964
|
+
}
|
|
965
|
+
result.push((xSq + xn * xn - 1 / Math.abs(this._curvature)) / safeDenom);
|
|
966
|
+
|
|
967
|
+
return this.projectToPoincare(result);
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// ==========================================================================
|
|
971
|
+
// Additional Operations
|
|
972
|
+
// ==========================================================================
|
|
973
|
+
|
|
974
|
+
/**
|
|
975
|
+
* Computes the geodesic midpoint of two points.
|
|
976
|
+
*
|
|
977
|
+
* @param a - First point
|
|
978
|
+
* @param b - Second point
|
|
979
|
+
* @returns Midpoint on the geodesic
|
|
980
|
+
*/
|
|
981
|
+
midpoint(a: number[], b: number[]): number[] {
|
|
982
|
+
// Use exponential map from a with half the tangent to b
|
|
983
|
+
const tangent = this.logMap(a, b);
|
|
984
|
+
const halfTangent = scale(tangent, 0.5);
|
|
985
|
+
return this.expMap(a, halfTangent);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
/**
|
|
989
|
+
* Computes the Frechet mean (centroid) of multiple points.
|
|
990
|
+
*
|
|
991
|
+
* Uses iterative gradient descent on the sum of squared distances.
|
|
992
|
+
*
|
|
993
|
+
* @param points - Array of points
|
|
994
|
+
* @param maxIter - Maximum iterations
|
|
995
|
+
* @param tol - Convergence tolerance
|
|
996
|
+
* @returns Frechet mean
|
|
997
|
+
*/
|
|
998
|
+
centroid(points: number[][], maxIter: number = 100, tol: number = 1e-8): number[] {
|
|
999
|
+
if (points.length === 0) {
|
|
1000
|
+
throw new Error('Cannot compute centroid of empty set');
|
|
1001
|
+
}
|
|
1002
|
+
if (points.length === 1) {
|
|
1003
|
+
return [...points[0]];
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
// Initialize with Euclidean mean, projected onto manifold
|
|
1007
|
+
let mean = zeros(points[0].length);
|
|
1008
|
+
for (const p of points) {
|
|
1009
|
+
mean = add(mean, p);
|
|
1010
|
+
}
|
|
1011
|
+
mean = this.projectToManifold(scale(mean, 1 / points.length));
|
|
1012
|
+
|
|
1013
|
+
// Iterative refinement
|
|
1014
|
+
for (let iter = 0; iter < maxIter; iter++) {
|
|
1015
|
+
// Compute sum of log maps
|
|
1016
|
+
let gradSum = zeros(points[0].length);
|
|
1017
|
+
for (const p of points) {
|
|
1018
|
+
const logP = this.logMap(mean, p);
|
|
1019
|
+
gradSum = add(gradSum, logP);
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
// Average gradient
|
|
1023
|
+
const avgGrad = scale(gradSum, 1 / points.length);
|
|
1024
|
+
const gradNorm = norm(avgGrad);
|
|
1025
|
+
|
|
1026
|
+
if (gradNorm < tol) {
|
|
1027
|
+
break;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
// Move mean in the direction of gradient
|
|
1031
|
+
mean = this.expMap(mean, avgGrad);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
return mean;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
/**
|
|
1038
|
+
* Parallel transports a tangent vector along a geodesic.
|
|
1039
|
+
*
|
|
1040
|
+
* @param vector - Tangent vector to transport
|
|
1041
|
+
* @param start - Starting point
|
|
1042
|
+
* @param end - Ending point
|
|
1043
|
+
* @returns Transported vector at the end point
|
|
1044
|
+
*/
|
|
1045
|
+
parallelTransport(vector: number[], start: number[], end: number[]): number[] {
|
|
1046
|
+
switch (this.model) {
|
|
1047
|
+
case 'poincare':
|
|
1048
|
+
return this.poincareParallelTransport(vector, start, end);
|
|
1049
|
+
case 'lorentz':
|
|
1050
|
+
return this.lorentzParallelTransport(vector, start, end);
|
|
1051
|
+
default:
|
|
1052
|
+
// For Klein and half-space, convert via Poincare
|
|
1053
|
+
return this.poincareParallelTransport(vector, start, end);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
/**
|
|
1058
|
+
* Parallel transport in Poincare ball.
|
|
1059
|
+
*/
|
|
1060
|
+
private poincareParallelTransport(vector: number[], start: number[], end: number[]): number[] {
|
|
1061
|
+
const c = Math.abs(this._curvature);
|
|
1062
|
+
|
|
1063
|
+
// Compute conformal factors
|
|
1064
|
+
const startSq = normSquared(start);
|
|
1065
|
+
const endSq = normSquared(end);
|
|
1066
|
+
const lambdaStart = 2 / (1 - c * startSq);
|
|
1067
|
+
const lambdaEnd = 2 / (1 - c * endSq);
|
|
1068
|
+
|
|
1069
|
+
// Gyration-based transport
|
|
1070
|
+
const negStart = scale(start, -1);
|
|
1071
|
+
const transported = this.mobiusAdd(end, this.mobiusAdd(negStart, scale(vector, 1)));
|
|
1072
|
+
|
|
1073
|
+
// Scale by ratio of conformal factors
|
|
1074
|
+
const scaleFactor = lambdaStart / lambdaEnd;
|
|
1075
|
+
return scale(sub(transported, end), scaleFactor);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
/**
|
|
1079
|
+
* Parallel transport in Lorentz model.
|
|
1080
|
+
*/
|
|
1081
|
+
private lorentzParallelTransport(vector: number[], start: number[], end: number[]): number[] {
|
|
1082
|
+
const logV = this.logMap(start, end);
|
|
1083
|
+
const logNorm = Math.sqrt(Math.max(0, this.lorentzInnerProduct(logV, logV)));
|
|
1084
|
+
|
|
1085
|
+
if (logNorm < this.eps) {
|
|
1086
|
+
return [...vector];
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
const inner1 = this.lorentzInnerProduct(end, vector);
|
|
1090
|
+
const inner2 = this.lorentzInnerProduct(start, vector);
|
|
1091
|
+
// Note: inner3 = this.lorentzInnerProduct(start, end) is used implicitly in the formula
|
|
1092
|
+
// via the geodesic distance relationship
|
|
1093
|
+
|
|
1094
|
+
const coeff = (inner1 - inner2 * Math.cosh(this._scale * logNorm)) /
|
|
1095
|
+
Math.sinh(this._scale * logNorm) / logNorm;
|
|
1096
|
+
|
|
1097
|
+
return add(vector, scale(add(start, scale(end, -1)), coeff));
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
/**
|
|
1101
|
+
* Computes a point along the geodesic from a to b at parameter t.
|
|
1102
|
+
*
|
|
1103
|
+
* @param a - Starting point
|
|
1104
|
+
* @param b - Ending point
|
|
1105
|
+
* @param t - Parameter in [0, 1]
|
|
1106
|
+
* @returns Point on geodesic
|
|
1107
|
+
*/
|
|
1108
|
+
geodesic(a: number[], b: number[], t: number): number[] {
|
|
1109
|
+
const tangent = this.logMap(a, b);
|
|
1110
|
+
const scaledTangent = scale(tangent, t);
|
|
1111
|
+
return this.expMap(a, scaledTangent);
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
/**
|
|
1115
|
+
* Computes the conformal factor (lambda) at a point in Poincare ball.
|
|
1116
|
+
*
|
|
1117
|
+
* lambda_p = 2 / (1 - |c| * ||p||^2)
|
|
1118
|
+
*
|
|
1119
|
+
* @param point - Point in Poincare ball
|
|
1120
|
+
* @returns Conformal factor
|
|
1121
|
+
*/
|
|
1122
|
+
conformalFactor(point: number[]): number {
|
|
1123
|
+
if (this.model !== 'poincare') {
|
|
1124
|
+
throw new Error('Conformal factor is only defined for Poincare ball model');
|
|
1125
|
+
}
|
|
1126
|
+
const c = Math.abs(this._curvature);
|
|
1127
|
+
const pSq = normSquared(point);
|
|
1128
|
+
return 2 / Math.max(1 - c * pSq, this.eps);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// ============================================================================
|
|
1133
|
+
// SQL Generation for RuVector PostgreSQL Functions
|
|
1134
|
+
// ============================================================================
|
|
1135
|
+
|
|
1136
|
+
/**
|
|
1137
|
+
* SQL function call builder for RuVector hyperbolic operations.
|
|
1138
|
+
*/
|
|
1139
|
+
export class HyperbolicSQL {
|
|
1140
|
+
/**
|
|
1141
|
+
* Generates SQL for Poincare distance computation.
|
|
1142
|
+
*
|
|
1143
|
+
* @param column - Vector column name
|
|
1144
|
+
* @param query - Query vector
|
|
1145
|
+
* @param curvature - Curvature parameter
|
|
1146
|
+
* @returns SQL expression
|
|
1147
|
+
*/
|
|
1148
|
+
static poincareDistance(column: string, query: number[], curvature: number): string {
|
|
1149
|
+
const vectorStr = `'[${query.join(',')}]'::vector`;
|
|
1150
|
+
return `ruvector_poincare_distance(${column}, ${vectorStr}, ${curvature})`;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
/**
|
|
1154
|
+
* Generates SQL for Lorentz distance computation.
|
|
1155
|
+
*
|
|
1156
|
+
* @param column - Vector column name
|
|
1157
|
+
* @param query - Query vector (with time component first)
|
|
1158
|
+
* @param curvature - Curvature parameter
|
|
1159
|
+
* @returns SQL expression
|
|
1160
|
+
*/
|
|
1161
|
+
static lorentzDistance(column: string, query: number[], curvature: number): string {
|
|
1162
|
+
const vectorStr = `'[${query.join(',')}]'::vector`;
|
|
1163
|
+
return `ruvector_lorentz_distance(${column}, ${vectorStr}, ${curvature})`;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
/**
|
|
1167
|
+
* Generates SQL for exponential map.
|
|
1168
|
+
*
|
|
1169
|
+
* @param baseColumn - Base point column
|
|
1170
|
+
* @param tangentColumn - Tangent vector column
|
|
1171
|
+
* @param model - Hyperbolic model
|
|
1172
|
+
* @param curvature - Curvature parameter
|
|
1173
|
+
* @returns SQL expression
|
|
1174
|
+
*/
|
|
1175
|
+
static expMap(
|
|
1176
|
+
baseColumn: string,
|
|
1177
|
+
tangentColumn: string,
|
|
1178
|
+
model: HyperbolicModel,
|
|
1179
|
+
curvature: number
|
|
1180
|
+
): string {
|
|
1181
|
+
const funcName = model === 'lorentz' ? 'ruvector_lorentz_exp_map' : 'ruvector_poincare_exp_map';
|
|
1182
|
+
return `${funcName}(${baseColumn}, ${tangentColumn}, ${curvature})`;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
/**
|
|
1186
|
+
* Generates SQL for logarithmic map.
|
|
1187
|
+
*
|
|
1188
|
+
* @param baseColumn - Base point column
|
|
1189
|
+
* @param targetColumn - Target point column
|
|
1190
|
+
* @param model - Hyperbolic model
|
|
1191
|
+
* @param curvature - Curvature parameter
|
|
1192
|
+
* @returns SQL expression
|
|
1193
|
+
*/
|
|
1194
|
+
static logMap(
|
|
1195
|
+
baseColumn: string,
|
|
1196
|
+
targetColumn: string,
|
|
1197
|
+
model: HyperbolicModel,
|
|
1198
|
+
curvature: number
|
|
1199
|
+
): string {
|
|
1200
|
+
const funcName = model === 'lorentz' ? 'ruvector_lorentz_log_map' : 'ruvector_poincare_log_map';
|
|
1201
|
+
return `${funcName}(${baseColumn}, ${targetColumn}, ${curvature})`;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
/**
|
|
1205
|
+
* Generates SQL for Mobius addition.
|
|
1206
|
+
*
|
|
1207
|
+
* @param aColumn - First point column
|
|
1208
|
+
* @param bColumn - Second point column
|
|
1209
|
+
* @param curvature - Curvature parameter
|
|
1210
|
+
* @returns SQL expression
|
|
1211
|
+
*/
|
|
1212
|
+
static mobiusAdd(aColumn: string, bColumn: string, curvature: number): string {
|
|
1213
|
+
return `ruvector_poincare_mobius_add(${aColumn}, ${bColumn}, ${curvature})`;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
/**
|
|
1217
|
+
* Generates SQL for Mobius matrix-vector multiplication.
|
|
1218
|
+
*
|
|
1219
|
+
* @param matrixColumn - Matrix column
|
|
1220
|
+
* @param vectorColumn - Vector column
|
|
1221
|
+
* @param curvature - Curvature parameter
|
|
1222
|
+
* @returns SQL expression
|
|
1223
|
+
*/
|
|
1224
|
+
static mobiusMatVec(matrixColumn: string, vectorColumn: string, curvature: number): string {
|
|
1225
|
+
return `ruvector_poincare_mobius_matvec(${matrixColumn}, ${vectorColumn}, ${curvature})`;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
/**
|
|
1229
|
+
* Generates SQL for parallel transport.
|
|
1230
|
+
*
|
|
1231
|
+
* @param vectorColumn - Vector to transport
|
|
1232
|
+
* @param startColumn - Starting point
|
|
1233
|
+
* @param endColumn - Ending point
|
|
1234
|
+
* @param model - Hyperbolic model
|
|
1235
|
+
* @param curvature - Curvature parameter
|
|
1236
|
+
* @returns SQL expression
|
|
1237
|
+
*/
|
|
1238
|
+
static parallelTransport(
|
|
1239
|
+
vectorColumn: string,
|
|
1240
|
+
startColumn: string,
|
|
1241
|
+
endColumn: string,
|
|
1242
|
+
model: HyperbolicModel,
|
|
1243
|
+
curvature: number
|
|
1244
|
+
): string {
|
|
1245
|
+
const funcName = model === 'lorentz'
|
|
1246
|
+
? 'ruvector_lorentz_parallel_transport'
|
|
1247
|
+
: 'ruvector_poincare_parallel_transport';
|
|
1248
|
+
return `${funcName}(${vectorColumn}, ${startColumn}, ${endColumn}, ${curvature})`;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
/**
|
|
1252
|
+
* Generates SQL for computing hyperbolic centroid.
|
|
1253
|
+
*
|
|
1254
|
+
* @param column - Vector column name
|
|
1255
|
+
* @param model - Hyperbolic model
|
|
1256
|
+
* @param curvature - Curvature parameter
|
|
1257
|
+
* @returns SQL expression (aggregate function)
|
|
1258
|
+
*/
|
|
1259
|
+
static centroid(column: string, model: HyperbolicModel, curvature: number): string {
|
|
1260
|
+
const funcName = model === 'lorentz'
|
|
1261
|
+
? 'ruvector_lorentz_centroid'
|
|
1262
|
+
: 'ruvector_poincare_centroid';
|
|
1263
|
+
return `${funcName}(${column}, ${curvature})`;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
/**
|
|
1267
|
+
* Generates SQL for model conversion (Poincare to Lorentz).
|
|
1268
|
+
*
|
|
1269
|
+
* @param column - Vector column
|
|
1270
|
+
* @param curvature - Curvature parameter
|
|
1271
|
+
* @returns SQL expression
|
|
1272
|
+
*/
|
|
1273
|
+
static poincareToLorentz(column: string, curvature: number): string {
|
|
1274
|
+
return `ruvector_poincare_to_lorentz(${column}, ${curvature})`;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
/**
|
|
1278
|
+
* Generates SQL for model conversion (Lorentz to Poincare).
|
|
1279
|
+
*
|
|
1280
|
+
* @param column - Vector column
|
|
1281
|
+
* @param curvature - Curvature parameter
|
|
1282
|
+
* @returns SQL expression
|
|
1283
|
+
*/
|
|
1284
|
+
static lorentzToPoincare(column: string, curvature: number): string {
|
|
1285
|
+
return `ruvector_lorentz_to_poincare(${column}, ${curvature})`;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
/**
|
|
1289
|
+
* Generates SQL for hyperbolic nearest neighbor search.
|
|
1290
|
+
*
|
|
1291
|
+
* @param tableName - Table name
|
|
1292
|
+
* @param vectorColumn - Vector column name
|
|
1293
|
+
* @param query - Query vector
|
|
1294
|
+
* @param k - Number of neighbors
|
|
1295
|
+
* @param model - Hyperbolic model
|
|
1296
|
+
* @param curvature - Curvature parameter
|
|
1297
|
+
* @param whereClause - Optional WHERE clause
|
|
1298
|
+
* @returns Complete SQL query
|
|
1299
|
+
*/
|
|
1300
|
+
static nearestNeighbors(
|
|
1301
|
+
tableName: string,
|
|
1302
|
+
vectorColumn: string,
|
|
1303
|
+
query: number[],
|
|
1304
|
+
k: number,
|
|
1305
|
+
model: HyperbolicModel,
|
|
1306
|
+
curvature: number,
|
|
1307
|
+
whereClause?: string
|
|
1308
|
+
): string {
|
|
1309
|
+
const distFunc = model === 'lorentz'
|
|
1310
|
+
? this.lorentzDistance(vectorColumn, query, curvature)
|
|
1311
|
+
: this.poincareDistance(vectorColumn, query, curvature);
|
|
1312
|
+
|
|
1313
|
+
const where = whereClause ? `WHERE ${whereClause}` : '';
|
|
1314
|
+
|
|
1315
|
+
return `
|
|
1316
|
+
SELECT *, ${distFunc} AS hyperbolic_distance
|
|
1317
|
+
FROM ${tableName}
|
|
1318
|
+
${where}
|
|
1319
|
+
ORDER BY hyperbolic_distance ASC
|
|
1320
|
+
LIMIT ${k}
|
|
1321
|
+
`.trim();
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
/**
|
|
1325
|
+
* Generates SQL for creating a hyperbolic embedding column.
|
|
1326
|
+
*
|
|
1327
|
+
* @param tableName - Table name
|
|
1328
|
+
* @param columnName - New column name
|
|
1329
|
+
* @param dimension - Vector dimension
|
|
1330
|
+
* @param model - Hyperbolic model
|
|
1331
|
+
* @returns SQL statement
|
|
1332
|
+
*/
|
|
1333
|
+
static createColumn(
|
|
1334
|
+
tableName: string,
|
|
1335
|
+
columnName: string,
|
|
1336
|
+
dimension: number,
|
|
1337
|
+
model: HyperbolicModel
|
|
1338
|
+
): string {
|
|
1339
|
+
const comment = `Hyperbolic embedding (${model} model, dim=${dimension})`;
|
|
1340
|
+
return `
|
|
1341
|
+
ALTER TABLE ${tableName}
|
|
1342
|
+
ADD COLUMN IF NOT EXISTS ${columnName} vector(${dimension});
|
|
1343
|
+
|
|
1344
|
+
COMMENT ON COLUMN ${tableName}.${columnName} IS '${comment}';
|
|
1345
|
+
`.trim();
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
/**
|
|
1349
|
+
* Generates SQL for batch hyperbolic distance computation.
|
|
1350
|
+
*
|
|
1351
|
+
* @param tableName - Table name
|
|
1352
|
+
* @param vectorColumn - Vector column name
|
|
1353
|
+
* @param queries - Array of query vectors
|
|
1354
|
+
* @param k - Number of neighbors per query
|
|
1355
|
+
* @param model - Hyperbolic model
|
|
1356
|
+
* @param curvature - Curvature parameter
|
|
1357
|
+
* @returns SQL query using LATERAL join
|
|
1358
|
+
*/
|
|
1359
|
+
static batchNearestNeighbors(
|
|
1360
|
+
tableName: string,
|
|
1361
|
+
vectorColumn: string,
|
|
1362
|
+
queries: number[][],
|
|
1363
|
+
k: number,
|
|
1364
|
+
model: HyperbolicModel,
|
|
1365
|
+
curvature: number
|
|
1366
|
+
): string {
|
|
1367
|
+
const queryValues = queries
|
|
1368
|
+
.map((q, i) => `(${i}, '[${q.join(',')}]'::vector)`)
|
|
1369
|
+
.join(',\n ');
|
|
1370
|
+
|
|
1371
|
+
const distFunc = model === 'lorentz'
|
|
1372
|
+
? `ruvector_lorentz_distance(t.${vectorColumn}, q.query_vec, ${curvature})`
|
|
1373
|
+
: `ruvector_poincare_distance(t.${vectorColumn}, q.query_vec, ${curvature})`;
|
|
1374
|
+
|
|
1375
|
+
return `
|
|
1376
|
+
WITH queries(query_id, query_vec) AS (
|
|
1377
|
+
VALUES
|
|
1378
|
+
${queryValues}
|
|
1379
|
+
)
|
|
1380
|
+
SELECT
|
|
1381
|
+
q.query_id,
|
|
1382
|
+
results.*
|
|
1383
|
+
FROM queries q
|
|
1384
|
+
CROSS JOIN LATERAL (
|
|
1385
|
+
SELECT
|
|
1386
|
+
t.*,
|
|
1387
|
+
${distFunc} AS hyperbolic_distance
|
|
1388
|
+
FROM ${tableName} t
|
|
1389
|
+
ORDER BY ${distFunc} ASC
|
|
1390
|
+
LIMIT ${k}
|
|
1391
|
+
) results
|
|
1392
|
+
ORDER BY q.query_id, results.hyperbolic_distance ASC
|
|
1393
|
+
`.trim();
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
// ============================================================================
|
|
1398
|
+
// Batch Operations for Embeddings
|
|
1399
|
+
// ============================================================================
|
|
1400
|
+
|
|
1401
|
+
/**
|
|
1402
|
+
* HyperbolicBatchProcessor handles batch operations on hyperbolic embeddings.
|
|
1403
|
+
*/
|
|
1404
|
+
export class HyperbolicBatchProcessor {
|
|
1405
|
+
private readonly space: HyperbolicSpace;
|
|
1406
|
+
|
|
1407
|
+
constructor(model: HyperbolicModel, curvature: number = DEFAULT_CURVATURE) {
|
|
1408
|
+
this.space = new HyperbolicSpace(model, curvature);
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
/**
|
|
1412
|
+
* Computes distances from a query point to multiple target points.
|
|
1413
|
+
*
|
|
1414
|
+
* @param query - Query point
|
|
1415
|
+
* @param targets - Array of target points
|
|
1416
|
+
* @returns Array of distances
|
|
1417
|
+
*/
|
|
1418
|
+
batchDistance(query: number[], targets: number[][]): number[] {
|
|
1419
|
+
return targets.map((target) => this.space.distance(query, target));
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
/**
|
|
1423
|
+
* Applies exponential map to multiple tangent vectors from a base point.
|
|
1424
|
+
*
|
|
1425
|
+
* @param base - Base point
|
|
1426
|
+
* @param tangents - Array of tangent vectors
|
|
1427
|
+
* @returns Array of resulting points
|
|
1428
|
+
*/
|
|
1429
|
+
batchExpMap(base: number[], tangents: number[][]): number[][] {
|
|
1430
|
+
return tangents.map((tangent) => this.space.expMap(base, tangent));
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
/**
|
|
1434
|
+
* Applies logarithmic map from a base point to multiple target points.
|
|
1435
|
+
*
|
|
1436
|
+
* @param base - Base point
|
|
1437
|
+
* @param targets - Array of target points
|
|
1438
|
+
* @returns Array of tangent vectors
|
|
1439
|
+
*/
|
|
1440
|
+
batchLogMap(base: number[], targets: number[][]): number[][] {
|
|
1441
|
+
return targets.map((target) => this.space.logMap(base, target));
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
/**
|
|
1445
|
+
* Projects multiple points onto the manifold.
|
|
1446
|
+
*
|
|
1447
|
+
* @param points - Array of points to project
|
|
1448
|
+
* @returns Array of projected points
|
|
1449
|
+
*/
|
|
1450
|
+
batchProject(points: number[][]): number[][] {
|
|
1451
|
+
return points.map((point) => this.space.projectToManifold(point));
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
/**
|
|
1455
|
+
* Converts multiple points between models.
|
|
1456
|
+
*
|
|
1457
|
+
* @param points - Array of points
|
|
1458
|
+
* @param fromModel - Source model
|
|
1459
|
+
* @param toModel - Target model
|
|
1460
|
+
* @returns Array of converted points
|
|
1461
|
+
*/
|
|
1462
|
+
batchConvert(
|
|
1463
|
+
points: number[][],
|
|
1464
|
+
fromModel: HyperbolicModel,
|
|
1465
|
+
toModel: HyperbolicModel
|
|
1466
|
+
): number[][] {
|
|
1467
|
+
if (fromModel === toModel) {
|
|
1468
|
+
return points.map((p) => [...p]);
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
// Handle direct conversions
|
|
1472
|
+
if (fromModel === 'poincare' && toModel === 'lorentz') {
|
|
1473
|
+
return points.map((p) => this.space.toLorentz(p));
|
|
1474
|
+
}
|
|
1475
|
+
if (fromModel === 'lorentz' && toModel === 'poincare') {
|
|
1476
|
+
return points.map((p) => this.space.toPoincare(p));
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
// For other conversions, go through Poincare as intermediate
|
|
1480
|
+
let intermediate = points;
|
|
1481
|
+
|
|
1482
|
+
// First convert to Poincare
|
|
1483
|
+
if (fromModel === 'lorentz') {
|
|
1484
|
+
intermediate = points.map((p) => this.space.toPoincare(p));
|
|
1485
|
+
} else if (fromModel === 'klein') {
|
|
1486
|
+
intermediate = points.map((p) => this.space.kleinToPoincare(p));
|
|
1487
|
+
} else if (fromModel === 'half_space') {
|
|
1488
|
+
intermediate = points.map((p) => this.space.halfSpaceToPoincare(p));
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
// Then convert from Poincare to target
|
|
1492
|
+
if (toModel === 'lorentz') {
|
|
1493
|
+
return intermediate.map((p) => this.space.toLorentz(p));
|
|
1494
|
+
} else if (toModel === 'klein') {
|
|
1495
|
+
return intermediate.map((p) => this.space.poincareToKlein(p));
|
|
1496
|
+
} else if (toModel === 'half_space') {
|
|
1497
|
+
return intermediate.map((p) => this.space.poincareToHalfSpace(p));
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
return intermediate;
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
/**
|
|
1504
|
+
* Performs k-nearest neighbor search in hyperbolic space.
|
|
1505
|
+
*
|
|
1506
|
+
* @param query - Query point
|
|
1507
|
+
* @param points - Array of candidate points with IDs
|
|
1508
|
+
* @param k - Number of neighbors
|
|
1509
|
+
* @returns K nearest neighbors sorted by distance
|
|
1510
|
+
*/
|
|
1511
|
+
knnSearch(
|
|
1512
|
+
query: number[],
|
|
1513
|
+
points: Array<{ id: string | number; point: number[]; metadata?: Record<string, unknown> }>,
|
|
1514
|
+
k: number
|
|
1515
|
+
): HyperbolicSearchResult[] {
|
|
1516
|
+
// Compute all distances
|
|
1517
|
+
const withDistances = points.map((p) => ({
|
|
1518
|
+
id: p.id,
|
|
1519
|
+
distance: this.space.distance(query, p.point),
|
|
1520
|
+
point: p.point,
|
|
1521
|
+
metadata: p.metadata,
|
|
1522
|
+
}));
|
|
1523
|
+
|
|
1524
|
+
// Sort by distance and take top k
|
|
1525
|
+
withDistances.sort((a, b) => a.distance - b.distance);
|
|
1526
|
+
return withDistances.slice(0, k);
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
/**
|
|
1530
|
+
* Computes the centroid of a set of points.
|
|
1531
|
+
*
|
|
1532
|
+
* @param points - Array of points
|
|
1533
|
+
* @param maxIter - Maximum iterations for iterative refinement
|
|
1534
|
+
* @returns Centroid point
|
|
1535
|
+
*/
|
|
1536
|
+
computeCentroid(points: number[][], maxIter: number = 100): number[] {
|
|
1537
|
+
return this.space.centroid(points, maxIter);
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
/**
|
|
1541
|
+
* Interpolates along geodesics between pairs of points.
|
|
1542
|
+
*
|
|
1543
|
+
* @param pairs - Array of [start, end] point pairs
|
|
1544
|
+
* @param t - Interpolation parameter (0 = start, 1 = end)
|
|
1545
|
+
* @returns Array of interpolated points
|
|
1546
|
+
*/
|
|
1547
|
+
batchGeodesic(pairs: [number[], number[]][], t: number): number[][] {
|
|
1548
|
+
return pairs.map(([a, b]) => this.space.geodesic(a, b, t));
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
/**
|
|
1552
|
+
* Performs Mobius addition on pairs of points.
|
|
1553
|
+
*
|
|
1554
|
+
* @param pairs - Array of [a, b] point pairs
|
|
1555
|
+
* @returns Array of Mobius sums
|
|
1556
|
+
*/
|
|
1557
|
+
batchMobiusAdd(pairs: [number[], number[]][]): number[][] {
|
|
1558
|
+
return pairs.map(([a, b]) => this.space.mobiusAdd(a, b));
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
// ============================================================================
|
|
1563
|
+
// Use Case Implementations
|
|
1564
|
+
// ============================================================================
|
|
1565
|
+
|
|
1566
|
+
/**
|
|
1567
|
+
* HierarchyEmbedder embeds tree-structured data in hyperbolic space.
|
|
1568
|
+
*
|
|
1569
|
+
* Useful for:
|
|
1570
|
+
* - Taxonomies (biological, product categories)
|
|
1571
|
+
* - Organizational charts
|
|
1572
|
+
* - File system hierarchies
|
|
1573
|
+
* - Knowledge graphs with hierarchical relations
|
|
1574
|
+
*/
|
|
1575
|
+
export class HierarchyEmbedder {
|
|
1576
|
+
private readonly space: HyperbolicSpace;
|
|
1577
|
+
private readonly dimension: number;
|
|
1578
|
+
|
|
1579
|
+
constructor(
|
|
1580
|
+
dimension: number,
|
|
1581
|
+
model: HyperbolicModel = 'poincare',
|
|
1582
|
+
curvature: number = DEFAULT_CURVATURE
|
|
1583
|
+
) {
|
|
1584
|
+
this.dimension = dimension;
|
|
1585
|
+
this.space = new HyperbolicSpace(model, curvature);
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
/**
|
|
1589
|
+
* Embeds a tree structure into hyperbolic space.
|
|
1590
|
+
*
|
|
1591
|
+
* Root is placed at the origin, children are placed along geodesics.
|
|
1592
|
+
*
|
|
1593
|
+
* @param tree - Tree structure with id, children, and optional data
|
|
1594
|
+
* @param angularSpread - Angular spread for children (default: 2*PI)
|
|
1595
|
+
* @returns Map of node IDs to embeddings
|
|
1596
|
+
*/
|
|
1597
|
+
embedTree<T extends { id: string; children?: T[]; data?: unknown }>(
|
|
1598
|
+
tree: T,
|
|
1599
|
+
angularSpread: number = 2 * Math.PI
|
|
1600
|
+
): Map<string, number[]> {
|
|
1601
|
+
const embeddings = new Map<string, number[]>();
|
|
1602
|
+
this.embedNode(tree, zeros(this.dimension), 0, angularSpread, embeddings);
|
|
1603
|
+
return embeddings;
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
private embedNode<T extends { id: string; children?: T[]; data?: unknown }>(
|
|
1607
|
+
node: T,
|
|
1608
|
+
position: number[],
|
|
1609
|
+
depth: number,
|
|
1610
|
+
angularSpread: number,
|
|
1611
|
+
embeddings: Map<string, number[]>
|
|
1612
|
+
): void {
|
|
1613
|
+
embeddings.set(node.id, position);
|
|
1614
|
+
|
|
1615
|
+
if (!node.children || node.children.length === 0) {
|
|
1616
|
+
return;
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
const numChildren = node.children.length;
|
|
1620
|
+
const angleStep = angularSpread / numChildren;
|
|
1621
|
+
const startAngle = -angularSpread / 2 + angleStep / 2;
|
|
1622
|
+
|
|
1623
|
+
// Distance to children decreases with depth to fit more nodes
|
|
1624
|
+
const childDistance = 0.5 / (depth + 1);
|
|
1625
|
+
|
|
1626
|
+
for (let i = 0; i < numChildren; i++) {
|
|
1627
|
+
const angle = startAngle + i * angleStep;
|
|
1628
|
+
|
|
1629
|
+
// Create tangent vector in the direction of the angle
|
|
1630
|
+
const tangent = zeros(this.dimension);
|
|
1631
|
+
tangent[0] = childDistance * Math.cos(angle);
|
|
1632
|
+
if (this.dimension > 1) {
|
|
1633
|
+
tangent[1] = childDistance * Math.sin(angle);
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
// Map to child position using exponential map
|
|
1637
|
+
const childPos = this.space.expMap(position, tangent);
|
|
1638
|
+
|
|
1639
|
+
// Recursively embed children with reduced angular spread
|
|
1640
|
+
this.embedNode(
|
|
1641
|
+
node.children[i],
|
|
1642
|
+
childPos,
|
|
1643
|
+
depth + 1,
|
|
1644
|
+
angularSpread / numChildren,
|
|
1645
|
+
embeddings
|
|
1646
|
+
);
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
/**
|
|
1651
|
+
* Gets the hyperbolic space instance for additional operations.
|
|
1652
|
+
*/
|
|
1653
|
+
getSpace(): HyperbolicSpace {
|
|
1654
|
+
return this.space;
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
/**
|
|
1659
|
+
* Recursive tree node interface for embedding.
|
|
1660
|
+
*/
|
|
1661
|
+
export interface TreeNode {
|
|
1662
|
+
id: string;
|
|
1663
|
+
children?: TreeNode[];
|
|
1664
|
+
data?: unknown;
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
/**
|
|
1668
|
+
* ASTEmbedder embeds Abstract Syntax Trees in hyperbolic space.
|
|
1669
|
+
*
|
|
1670
|
+
* Preserves the hierarchical structure of code, enabling:
|
|
1671
|
+
* - Similar code search
|
|
1672
|
+
* - Code clone detection
|
|
1673
|
+
* - Structural diff operations
|
|
1674
|
+
*/
|
|
1675
|
+
export class ASTEmbedder extends HierarchyEmbedder {
|
|
1676
|
+
/**
|
|
1677
|
+
* Embeds an AST node structure.
|
|
1678
|
+
*
|
|
1679
|
+
* @param ast - AST with type, children, and optional metadata
|
|
1680
|
+
* @returns Map of node paths to embeddings
|
|
1681
|
+
*/
|
|
1682
|
+
embedAST(ast: ASTNode): Map<string, number[]> {
|
|
1683
|
+
const treeNode = this.astToTree(ast, '');
|
|
1684
|
+
return this.embedTree(treeNode);
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
private astToTree(node: ASTNode, path: string): TreeNode {
|
|
1688
|
+
const id = path ? `${path}/${node.type}` : node.type;
|
|
1689
|
+
const children = node.children?.map((child, i) =>
|
|
1690
|
+
this.astToTree(child, `${id}[${i}]`)
|
|
1691
|
+
);
|
|
1692
|
+
|
|
1693
|
+
return {
|
|
1694
|
+
id,
|
|
1695
|
+
children,
|
|
1696
|
+
data: {
|
|
1697
|
+
type: node.type,
|
|
1698
|
+
value: node.value,
|
|
1699
|
+
location: node.location,
|
|
1700
|
+
},
|
|
1701
|
+
};
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
/**
|
|
1706
|
+
* AST node structure for embedding.
|
|
1707
|
+
*/
|
|
1708
|
+
export interface ASTNode {
|
|
1709
|
+
/** Node type (e.g., 'FunctionDeclaration', 'IfStatement') */
|
|
1710
|
+
type: string;
|
|
1711
|
+
/** Optional value (for literals, identifiers) */
|
|
1712
|
+
value?: unknown;
|
|
1713
|
+
/** Child nodes */
|
|
1714
|
+
children?: ASTNode[];
|
|
1715
|
+
/** Source location */
|
|
1716
|
+
location?: { start: number; end: number };
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
/**
|
|
1720
|
+
* DependencyGraphEmbedder embeds package/module dependency graphs.
|
|
1721
|
+
*
|
|
1722
|
+
* Captures both direct and transitive dependencies in hyperbolic space.
|
|
1723
|
+
*/
|
|
1724
|
+
export class DependencyGraphEmbedder {
|
|
1725
|
+
private readonly space: HyperbolicSpace;
|
|
1726
|
+
private readonly dimension: number;
|
|
1727
|
+
|
|
1728
|
+
constructor(
|
|
1729
|
+
dimension: number,
|
|
1730
|
+
model: HyperbolicModel = 'poincare',
|
|
1731
|
+
curvature: number = DEFAULT_CURVATURE
|
|
1732
|
+
) {
|
|
1733
|
+
this.dimension = dimension;
|
|
1734
|
+
this.space = new HyperbolicSpace(model, curvature);
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
/**
|
|
1738
|
+
* Embeds a dependency graph.
|
|
1739
|
+
*
|
|
1740
|
+
* @param graph - Map of package names to their dependencies
|
|
1741
|
+
* @param root - Optional root package (placed at origin)
|
|
1742
|
+
* @returns Map of package names to embeddings
|
|
1743
|
+
*/
|
|
1744
|
+
embedDependencyGraph(
|
|
1745
|
+
graph: Map<string, string[]>,
|
|
1746
|
+
root?: string
|
|
1747
|
+
): Map<string, number[]> {
|
|
1748
|
+
const embeddings = new Map<string, number[]>();
|
|
1749
|
+
|
|
1750
|
+
// Find root nodes (packages with no dependents)
|
|
1751
|
+
const roots = this.findRoots(graph);
|
|
1752
|
+
|
|
1753
|
+
if (root && graph.has(root)) {
|
|
1754
|
+
// Use specified root
|
|
1755
|
+
const processed = new Set<string>();
|
|
1756
|
+
this.embedFromRoot(root, zeros(this.dimension), graph, embeddings, processed, 0);
|
|
1757
|
+
} else {
|
|
1758
|
+
// Embed from all roots
|
|
1759
|
+
const angleStep = (2 * Math.PI) / roots.length;
|
|
1760
|
+
const processed = new Set<string>();
|
|
1761
|
+
|
|
1762
|
+
roots.forEach((rootNode, i) => {
|
|
1763
|
+
const angle = i * angleStep;
|
|
1764
|
+
const tangent = zeros(this.dimension);
|
|
1765
|
+
tangent[0] = 0.3 * Math.cos(angle);
|
|
1766
|
+
if (this.dimension > 1) {
|
|
1767
|
+
tangent[1] = 0.3 * Math.sin(angle);
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
const rootPos = this.space.expMap(zeros(this.dimension), tangent);
|
|
1771
|
+
this.embedFromRoot(rootNode, rootPos, graph, embeddings, processed, 0);
|
|
1772
|
+
});
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
return embeddings;
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
private findRoots(graph: Map<string, string[]>): string[] {
|
|
1779
|
+
const dependents = new Set<string>();
|
|
1780
|
+
for (const deps of graph.values()) {
|
|
1781
|
+
deps.forEach((d) => dependents.add(d));
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
return Array.from(graph.keys()).filter((k) => !dependents.has(k));
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
private embedFromRoot(
|
|
1788
|
+
node: string,
|
|
1789
|
+
position: number[],
|
|
1790
|
+
graph: Map<string, string[]>,
|
|
1791
|
+
embeddings: Map<string, number[]>,
|
|
1792
|
+
processed: Set<string>,
|
|
1793
|
+
depth: number
|
|
1794
|
+
): void {
|
|
1795
|
+
if (processed.has(node)) {
|
|
1796
|
+
return;
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
processed.add(node);
|
|
1800
|
+
embeddings.set(node, position);
|
|
1801
|
+
|
|
1802
|
+
const deps = graph.get(node) || [];
|
|
1803
|
+
const numDeps = deps.length;
|
|
1804
|
+
|
|
1805
|
+
if (numDeps === 0) {
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
const angleStep = (2 * Math.PI) / numDeps;
|
|
1810
|
+
const depDistance = 0.4 / (depth + 1);
|
|
1811
|
+
|
|
1812
|
+
deps.forEach((dep, i) => {
|
|
1813
|
+
if (processed.has(dep)) {
|
|
1814
|
+
return;
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
const angle = i * angleStep;
|
|
1818
|
+
const tangent = zeros(this.dimension);
|
|
1819
|
+
tangent[0] = depDistance * Math.cos(angle);
|
|
1820
|
+
if (this.dimension > 1) {
|
|
1821
|
+
tangent[1] = depDistance * Math.sin(angle);
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
const depPos = this.space.expMap(position, tangent);
|
|
1825
|
+
this.embedFromRoot(dep, depPos, graph, embeddings, processed, depth + 1);
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
/**
|
|
1830
|
+
* Computes the dependency distance between two packages.
|
|
1831
|
+
*
|
|
1832
|
+
* @param a - First package name
|
|
1833
|
+
* @param b - Second package name
|
|
1834
|
+
* @param embeddings - Pre-computed embeddings
|
|
1835
|
+
* @returns Hyperbolic distance
|
|
1836
|
+
*/
|
|
1837
|
+
dependencyDistance(
|
|
1838
|
+
a: string,
|
|
1839
|
+
b: string,
|
|
1840
|
+
embeddings: Map<string, number[]>
|
|
1841
|
+
): number {
|
|
1842
|
+
const embA = embeddings.get(a);
|
|
1843
|
+
const embB = embeddings.get(b);
|
|
1844
|
+
|
|
1845
|
+
if (!embA || !embB) {
|
|
1846
|
+
throw new Error(`Embedding not found for ${!embA ? a : b}`);
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
return this.space.distance(embA, embB);
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
/**
|
|
1853
|
+
* Gets the hyperbolic space instance.
|
|
1854
|
+
*/
|
|
1855
|
+
getSpace(): HyperbolicSpace {
|
|
1856
|
+
return this.space;
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
// ============================================================================
|
|
1861
|
+
// Factory Functions
|
|
1862
|
+
// ============================================================================
|
|
1863
|
+
|
|
1864
|
+
/**
|
|
1865
|
+
* Creates a HyperbolicSpace instance from configuration.
|
|
1866
|
+
*
|
|
1867
|
+
* @param config - Hyperbolic space configuration
|
|
1868
|
+
* @returns Configured HyperbolicSpace instance
|
|
1869
|
+
*/
|
|
1870
|
+
export function createHyperbolicSpace(config: HyperbolicSpaceConfig): HyperbolicSpace {
|
|
1871
|
+
return new HyperbolicSpace(
|
|
1872
|
+
config.model,
|
|
1873
|
+
config.curvature,
|
|
1874
|
+
config.eps,
|
|
1875
|
+
config.maxNorm
|
|
1876
|
+
);
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
/**
|
|
1880
|
+
* Creates a HyperbolicSpace instance from HyperbolicEmbedding type.
|
|
1881
|
+
*
|
|
1882
|
+
* @param embedding - Hyperbolic embedding configuration
|
|
1883
|
+
* @returns Configured HyperbolicSpace instance
|
|
1884
|
+
*/
|
|
1885
|
+
export function fromEmbeddingConfig(embedding: HyperbolicEmbedding): HyperbolicSpace {
|
|
1886
|
+
return new HyperbolicSpace(
|
|
1887
|
+
embedding.model,
|
|
1888
|
+
embedding.curvature,
|
|
1889
|
+
embedding.params?.eps,
|
|
1890
|
+
embedding.params?.maxNorm
|
|
1891
|
+
);
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
/**
|
|
1895
|
+
* Validates that a point is valid for the given hyperbolic model.
|
|
1896
|
+
*
|
|
1897
|
+
* @param point - Point to validate
|
|
1898
|
+
* @param model - Hyperbolic model
|
|
1899
|
+
* @param curvature - Curvature parameter
|
|
1900
|
+
* @returns True if valid
|
|
1901
|
+
*/
|
|
1902
|
+
export function validatePoint(
|
|
1903
|
+
point: number[],
|
|
1904
|
+
model: HyperbolicModel,
|
|
1905
|
+
curvature: number
|
|
1906
|
+
): boolean {
|
|
1907
|
+
const c = Math.abs(curvature);
|
|
1908
|
+
const eps = DEFAULT_EPS;
|
|
1909
|
+
|
|
1910
|
+
switch (model) {
|
|
1911
|
+
case 'poincare': {
|
|
1912
|
+
const n = norm(point);
|
|
1913
|
+
return n < 1 - eps;
|
|
1914
|
+
}
|
|
1915
|
+
case 'lorentz': {
|
|
1916
|
+
// Check Lorentz constraint: -x0^2 + sum(xi^2) = -1/c
|
|
1917
|
+
let spatialSq = 0;
|
|
1918
|
+
for (let i = 1; i < point.length; i++) {
|
|
1919
|
+
spatialSq += point[i] * point[i];
|
|
1920
|
+
}
|
|
1921
|
+
const constraint = -point[0] * point[0] + spatialSq;
|
|
1922
|
+
return Math.abs(constraint + 1 / c) < eps * 1000;
|
|
1923
|
+
}
|
|
1924
|
+
case 'klein': {
|
|
1925
|
+
const n = norm(point);
|
|
1926
|
+
return n < 1 - eps;
|
|
1927
|
+
}
|
|
1928
|
+
case 'half_space': {
|
|
1929
|
+
return point[point.length - 1] > eps;
|
|
1930
|
+
}
|
|
1931
|
+
default:
|
|
1932
|
+
return false;
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
// ============================================================================
|
|
1937
|
+
// Re-exports from types
|
|
1938
|
+
// ============================================================================
|
|
1939
|
+
|
|
1940
|
+
export type {
|
|
1941
|
+
HyperbolicModel,
|
|
1942
|
+
HyperbolicEmbedding,
|
|
1943
|
+
HyperbolicInput,
|
|
1944
|
+
HyperbolicOutput,
|
|
1945
|
+
HyperbolicOperation,
|
|
1946
|
+
HyperbolicParams,
|
|
1947
|
+
HyperbolicDistance,
|
|
1948
|
+
} from './types.js';
|