Squashed 'vendor/ruvector/' content from commit b64c2172

git-subtree-dir: vendor/ruvector
git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
This commit is contained in:
ruv
2026-02-28 14:39:40 -05:00
commit d803bfe2b1
7854 changed files with 3522914 additions and 0 deletions

View File

@@ -0,0 +1,393 @@
/**
* Hyperbolic Geometry Commands
* CLI commands for hyperbolic embedding operations (Poincare ball, Lorentz model)
*
* NOTE: These functions require the hyperbolic geometry module to be enabled
* in the RuVector PostgreSQL extension. Currently in development.
*/
import chalk from 'chalk';
import ora from 'ora';
import Table from 'cli-table3';
import type { RuVectorClient } from '../client.js';
const HYPERBOLIC_REQUIRES_EXTENSION_MSG = `
${chalk.yellow('Hyperbolic geometry requires the RuVector PostgreSQL extension.')}
Ensure you have:
1. Built the ruvector-postgres Docker image
2. Started a container with the extension installed
3. Run: CREATE EXTENSION ruvector;
Available functions:
- ruvector_poincare_distance(a, b, curvature)
- ruvector_lorentz_distance(a, b, curvature)
- ruvector_mobius_add(a, b, curvature)
- ruvector_exp_map(base, tangent, curvature)
- ruvector_log_map(base, target, curvature)
- ruvector_poincare_to_lorentz(poincare, curvature)
- ruvector_lorentz_to_poincare(lorentz, curvature)
- ruvector_minkowski_dot(a, b)
${chalk.gray('See: https://github.com/ruvnet/ruvector for setup instructions.')}
`;
function checkHyperbolicAvailable(): boolean {
// Hyperbolic geometry functions are now implemented in the PostgreSQL extension
// The functions are available in ruvector--0.1.0.sql
return true;
}
export interface PoincareDistanceOptions {
a: string;
b: string;
curvature?: string;
}
export interface LorentzDistanceOptions {
a: string;
b: string;
curvature?: string;
}
export interface MobiusAddOptions {
a: string;
b: string;
curvature?: string;
}
export interface ExpMapOptions {
base: string;
tangent: string;
curvature?: string;
}
export interface LogMapOptions {
base: string;
target: string;
curvature?: string;
}
export interface ConvertOptions {
vector: string;
curvature?: string;
}
export class HyperbolicCommands {
static async poincareDistance(
client: RuVectorClient,
options: PoincareDistanceOptions
): Promise<void> {
if (!checkHyperbolicAvailable()) {
console.log(HYPERBOLIC_REQUIRES_EXTENSION_MSG);
return;
}
const spinner = ora('Computing Poincare distance...').start();
try {
await client.connect();
const a = JSON.parse(options.a);
const b = JSON.parse(options.b);
const curvature = options.curvature ? parseFloat(options.curvature) : -1.0;
const distance = await client.poincareDistance(a, b, curvature);
spinner.succeed(chalk.green('Poincare distance computed'));
console.log(chalk.bold.blue('\nPoincare Distance:'));
console.log(chalk.gray('-'.repeat(40)));
console.log(` ${chalk.green('Distance:')} ${distance.toFixed(6)}`);
console.log(` ${chalk.green('Curvature:')} ${curvature}`);
console.log(` ${chalk.green('Dimension:')} ${a.length}`);
} catch (err) {
spinner.fail(chalk.red('Distance computation failed'));
console.error(chalk.red((err as Error).message));
} finally {
await client.disconnect();
}
}
static async lorentzDistance(
client: RuVectorClient,
options: LorentzDistanceOptions
): Promise<void> {
if (!checkHyperbolicAvailable()) {
console.log(HYPERBOLIC_REQUIRES_EXTENSION_MSG);
return;
}
const spinner = ora('Computing Lorentz distance...').start();
try {
await client.connect();
const a = JSON.parse(options.a);
const b = JSON.parse(options.b);
const curvature = options.curvature ? parseFloat(options.curvature) : -1.0;
const distance = await client.lorentzDistance(a, b, curvature);
spinner.succeed(chalk.green('Lorentz distance computed'));
console.log(chalk.bold.blue('\nLorentz Distance:'));
console.log(chalk.gray('-'.repeat(40)));
console.log(` ${chalk.green('Distance:')} ${distance.toFixed(6)}`);
console.log(` ${chalk.green('Curvature:')} ${curvature}`);
console.log(` ${chalk.green('Dimension:')} ${a.length}`);
} catch (err) {
spinner.fail(chalk.red('Distance computation failed'));
console.error(chalk.red((err as Error).message));
} finally {
await client.disconnect();
}
}
static async mobiusAdd(
client: RuVectorClient,
options: MobiusAddOptions
): Promise<void> {
if (!checkHyperbolicAvailable()) {
console.log(HYPERBOLIC_REQUIRES_EXTENSION_MSG);
return;
}
const spinner = ora('Computing Mobius addition...').start();
try {
await client.connect();
const a = JSON.parse(options.a);
const b = JSON.parse(options.b);
const curvature = options.curvature ? parseFloat(options.curvature) : -1.0;
const result = await client.mobiusAdd(a, b, curvature);
spinner.succeed(chalk.green('Mobius addition computed'));
console.log(chalk.bold.blue('\nMobius Addition Result:'));
console.log(chalk.gray('-'.repeat(40)));
console.log(` ${chalk.green('Curvature:')} ${curvature}`);
console.log(` ${chalk.green('Result:')} [${result.map((v: number) => v.toFixed(4)).join(', ')}]`);
// Verify result is in ball
const norm = Math.sqrt(result.reduce((sum: number, v: number) => sum + v * v, 0));
console.log(` ${chalk.green('Result Norm:')} ${norm.toFixed(6)} ${norm < 1 ? chalk.green('(valid)') : chalk.red('(invalid)')}`);
} catch (err) {
spinner.fail(chalk.red('Mobius addition failed'));
console.error(chalk.red((err as Error).message));
} finally {
await client.disconnect();
}
}
static async expMap(
client: RuVectorClient,
options: ExpMapOptions
): Promise<void> {
if (!checkHyperbolicAvailable()) {
console.log(HYPERBOLIC_REQUIRES_EXTENSION_MSG);
return;
}
const spinner = ora('Computing exponential map...').start();
try {
await client.connect();
const base = JSON.parse(options.base);
const tangent = JSON.parse(options.tangent);
const curvature = options.curvature ? parseFloat(options.curvature) : -1.0;
const result = await client.expMap(base, tangent, curvature);
spinner.succeed(chalk.green('Exponential map computed'));
console.log(chalk.bold.blue('\nExponential Map Result:'));
console.log(chalk.gray('-'.repeat(40)));
console.log(` ${chalk.green('Base Point:')} [${base.map((v: number) => v.toFixed(4)).join(', ')}]`);
console.log(` ${chalk.green('Tangent Vector:')} [${tangent.map((v: number) => v.toFixed(4)).join(', ')}]`);
console.log(` ${chalk.green('Result (on manifold):')} [${result.map((v: number) => v.toFixed(4)).join(', ')}]`);
} catch (err) {
spinner.fail(chalk.red('Exponential map failed'));
console.error(chalk.red((err as Error).message));
} finally {
await client.disconnect();
}
}
static async logMap(
client: RuVectorClient,
options: LogMapOptions
): Promise<void> {
if (!checkHyperbolicAvailable()) {
console.log(HYPERBOLIC_REQUIRES_EXTENSION_MSG);
return;
}
const spinner = ora('Computing logarithmic map...').start();
try {
await client.connect();
const base = JSON.parse(options.base);
const target = JSON.parse(options.target);
const curvature = options.curvature ? parseFloat(options.curvature) : -1.0;
const result = await client.logMap(base, target, curvature);
spinner.succeed(chalk.green('Logarithmic map computed'));
console.log(chalk.bold.blue('\nLogarithmic Map Result:'));
console.log(chalk.gray('-'.repeat(40)));
console.log(` ${chalk.green('Base Point:')} [${base.map((v: number) => v.toFixed(4)).join(', ')}]`);
console.log(` ${chalk.green('Target Point:')} [${target.map((v: number) => v.toFixed(4)).join(', ')}]`);
console.log(` ${chalk.green('Tangent (at base):')} [${result.map((v: number) => v.toFixed(4)).join(', ')}]`);
} catch (err) {
spinner.fail(chalk.red('Logarithmic map failed'));
console.error(chalk.red((err as Error).message));
} finally {
await client.disconnect();
}
}
static async poincareToLorentz(
client: RuVectorClient,
options: ConvertOptions
): Promise<void> {
if (!checkHyperbolicAvailable()) {
console.log(HYPERBOLIC_REQUIRES_EXTENSION_MSG);
return;
}
const spinner = ora('Converting Poincare to Lorentz...').start();
try {
await client.connect();
const poincare = JSON.parse(options.vector);
const curvature = options.curvature ? parseFloat(options.curvature) : -1.0;
const lorentz = await client.poincareToLorentz(poincare, curvature);
spinner.succeed(chalk.green('Conversion completed'));
console.log(chalk.bold.blue('\nCoordinate Conversion:'));
console.log(chalk.gray('-'.repeat(40)));
console.log(` ${chalk.green('Poincare (ball):')} [${poincare.map((v: number) => v.toFixed(4)).join(', ')}]`);
console.log(` ${chalk.green('Lorentz (hyperboloid):')} [${lorentz.map((v: number) => v.toFixed(4)).join(', ')}]`);
console.log(` ${chalk.green('Dimension change:')} ${poincare.length} -> ${lorentz.length}`);
} catch (err) {
spinner.fail(chalk.red('Conversion failed'));
console.error(chalk.red((err as Error).message));
} finally {
await client.disconnect();
}
}
static async lorentzToPoincare(
client: RuVectorClient,
options: ConvertOptions
): Promise<void> {
if (!checkHyperbolicAvailable()) {
console.log(HYPERBOLIC_REQUIRES_EXTENSION_MSG);
return;
}
const spinner = ora('Converting Lorentz to Poincare...').start();
try {
await client.connect();
const lorentz = JSON.parse(options.vector);
const curvature = options.curvature ? parseFloat(options.curvature) : -1.0;
const poincare = await client.lorentzToPoincare(lorentz, curvature);
spinner.succeed(chalk.green('Conversion completed'));
console.log(chalk.bold.blue('\nCoordinate Conversion:'));
console.log(chalk.gray('-'.repeat(40)));
console.log(` ${chalk.green('Lorentz (hyperboloid):')} [${lorentz.map((v: number) => v.toFixed(4)).join(', ')}]`);
console.log(` ${chalk.green('Poincare (ball):')} [${poincare.map((v: number) => v.toFixed(4)).join(', ')}]`);
console.log(` ${chalk.green('Dimension change:')} ${lorentz.length} -> ${poincare.length}`);
} catch (err) {
spinner.fail(chalk.red('Conversion failed'));
console.error(chalk.red((err as Error).message));
} finally {
await client.disconnect();
}
}
static async minkowskiDot(
client: RuVectorClient,
a: string,
b: string
): Promise<void> {
if (!checkHyperbolicAvailable()) {
console.log(HYPERBOLIC_REQUIRES_EXTENSION_MSG);
return;
}
const spinner = ora('Computing Minkowski inner product...').start();
try {
await client.connect();
const vecA = JSON.parse(a);
const vecB = JSON.parse(b);
const result = await client.minkowskiDot(vecA, vecB);
spinner.succeed(chalk.green('Minkowski inner product computed'));
console.log(chalk.bold.blue('\nMinkowski Inner Product:'));
console.log(chalk.gray('-'.repeat(40)));
console.log(` ${chalk.green('Result:')} ${result.toFixed(6)}`);
console.log(` ${chalk.gray('Note:')} Uses signature (-,+,+,...,+)`);
} catch (err) {
spinner.fail(chalk.red('Computation failed'));
console.error(chalk.red((err as Error).message));
} finally {
await client.disconnect();
}
}
static showHelp(): void {
console.log(chalk.bold.blue('\nHyperbolic Geometry Operations:'));
console.log(chalk.gray('-'.repeat(60)));
console.log(`
${chalk.yellow('Overview:')}
Hyperbolic space is ideal for embedding hierarchical data like
taxonomies, organizational charts, and knowledge graphs.
${chalk.yellow('Models:')}
${chalk.green('Poincare Ball')} - Unit ball model, good for visualization
${chalk.green('Lorentz/Hyperboloid')} - Numerically stable, good for training
${chalk.yellow('Curvature:')}
Default curvature is -1.0. More negative = more "curved" space.
Must always be negative for hyperbolic geometry.
${chalk.yellow('Commands:')}
${chalk.green('hyperbolic poincare-distance')} - Distance in Poincare ball
${chalk.green('hyperbolic lorentz-distance')} - Distance on hyperboloid
${chalk.green('hyperbolic mobius-add')} - Hyperbolic addition
${chalk.green('hyperbolic exp-map')} - Tangent to manifold
${chalk.green('hyperbolic log-map')} - Manifold to tangent
${chalk.green('hyperbolic poincare-to-lorentz')} - Convert coordinates
${chalk.green('hyperbolic lorentz-to-poincare')} - Convert coordinates
${chalk.green('hyperbolic minkowski-dot')} - Minkowski inner product
${chalk.yellow('Use Cases:')}
- Hierarchical clustering
- Knowledge graph embeddings
- Taxonomy representation
- Social network analysis
`);
}
}
export default HyperbolicCommands;