@sparkleideas/plugins 3.0.0-alpha.8

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.
Files changed (80) hide show
  1. package/README.md +401 -0
  2. package/__tests__/collection-manager.test.ts +332 -0
  3. package/__tests__/dependency-graph.test.ts +434 -0
  4. package/__tests__/enhanced-plugin-registry.test.ts +488 -0
  5. package/__tests__/plugin-registry.test.ts +368 -0
  6. package/__tests__/ruvector-bridge.test.ts +2429 -0
  7. package/__tests__/ruvector-integration.test.ts +1602 -0
  8. package/__tests__/ruvector-migrations.test.ts +1099 -0
  9. package/__tests__/ruvector-quantization.test.ts +846 -0
  10. package/__tests__/ruvector-streaming.test.ts +1088 -0
  11. package/__tests__/sdk.test.ts +325 -0
  12. package/__tests__/security.test.ts +348 -0
  13. package/__tests__/utils/ruvector-test-utils.ts +860 -0
  14. package/examples/plugin-creator/index.ts +636 -0
  15. package/examples/plugin-creator/plugin-creator.test.ts +312 -0
  16. package/examples/ruvector/README.md +288 -0
  17. package/examples/ruvector/attention-patterns.ts +394 -0
  18. package/examples/ruvector/basic-usage.ts +288 -0
  19. package/examples/ruvector/docker-compose.yml +75 -0
  20. package/examples/ruvector/gnn-analysis.ts +501 -0
  21. package/examples/ruvector/hyperbolic-hierarchies.ts +557 -0
  22. package/examples/ruvector/init-db.sql +119 -0
  23. package/examples/ruvector/quantization.ts +680 -0
  24. package/examples/ruvector/self-learning.ts +447 -0
  25. package/examples/ruvector/semantic-search.ts +576 -0
  26. package/examples/ruvector/streaming-large-data.ts +507 -0
  27. package/examples/ruvector/transactions.ts +594 -0
  28. package/examples/ruvector-plugins/hook-pattern-library.ts +486 -0
  29. package/examples/ruvector-plugins/index.ts +79 -0
  30. package/examples/ruvector-plugins/intent-router.ts +354 -0
  31. package/examples/ruvector-plugins/mcp-tool-optimizer.ts +424 -0
  32. package/examples/ruvector-plugins/reasoning-bank.ts +657 -0
  33. package/examples/ruvector-plugins/ruvector-plugins.test.ts +518 -0
  34. package/examples/ruvector-plugins/semantic-code-search.ts +498 -0
  35. package/examples/ruvector-plugins/shared/index.ts +20 -0
  36. package/examples/ruvector-plugins/shared/vector-utils.ts +257 -0
  37. package/examples/ruvector-plugins/sona-learning.ts +445 -0
  38. package/package.json +97 -0
  39. package/src/collections/collection-manager.ts +661 -0
  40. package/src/collections/index.ts +56 -0
  41. package/src/collections/official/index.ts +1040 -0
  42. package/src/core/base-plugin.ts +416 -0
  43. package/src/core/plugin-interface.ts +215 -0
  44. package/src/hooks/index.ts +685 -0
  45. package/src/index.ts +378 -0
  46. package/src/integrations/agentic-flow.ts +743 -0
  47. package/src/integrations/index.ts +88 -0
  48. package/src/integrations/ruvector/ARCHITECTURE.md +1245 -0
  49. package/src/integrations/ruvector/attention-advanced.ts +1040 -0
  50. package/src/integrations/ruvector/attention-executor.ts +782 -0
  51. package/src/integrations/ruvector/attention-mechanisms.ts +757 -0
  52. package/src/integrations/ruvector/attention.ts +1063 -0
  53. package/src/integrations/ruvector/gnn.ts +3050 -0
  54. package/src/integrations/ruvector/hyperbolic.ts +1948 -0
  55. package/src/integrations/ruvector/index.ts +394 -0
  56. package/src/integrations/ruvector/migrations/001_create_extension.sql +135 -0
  57. package/src/integrations/ruvector/migrations/002_create_vector_tables.sql +259 -0
  58. package/src/integrations/ruvector/migrations/003_create_indices.sql +328 -0
  59. package/src/integrations/ruvector/migrations/004_create_functions.sql +598 -0
  60. package/src/integrations/ruvector/migrations/005_create_attention_functions.sql +654 -0
  61. package/src/integrations/ruvector/migrations/006_create_gnn_functions.sql +728 -0
  62. package/src/integrations/ruvector/migrations/007_create_hyperbolic_functions.sql +762 -0
  63. package/src/integrations/ruvector/migrations/index.ts +35 -0
  64. package/src/integrations/ruvector/migrations/migrations.ts +647 -0
  65. package/src/integrations/ruvector/quantization.ts +2036 -0
  66. package/src/integrations/ruvector/ruvector-bridge.ts +2000 -0
  67. package/src/integrations/ruvector/self-learning.ts +2376 -0
  68. package/src/integrations/ruvector/streaming.ts +1737 -0
  69. package/src/integrations/ruvector/types.ts +1945 -0
  70. package/src/providers/index.ts +643 -0
  71. package/src/registry/dependency-graph.ts +568 -0
  72. package/src/registry/enhanced-plugin-registry.ts +994 -0
  73. package/src/registry/plugin-registry.ts +604 -0
  74. package/src/sdk/index.ts +563 -0
  75. package/src/security/index.ts +594 -0
  76. package/src/types/index.ts +446 -0
  77. package/src/workers/index.ts +700 -0
  78. package/tmp.json +0 -0
  79. package/tsconfig.json +25 -0
  80. 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';