From 3200ea25787a922494b5f7de812aa8cd4b778f97 Mon Sep 17 00:00:00 2001 From: rUv Date: Sun, 11 Jan 2026 17:21:16 +0000 Subject: [PATCH] fix: Update ruvector-math-wasm to use @ruvector/math-wasm scoped package - Rename npm package from ruvector-math-wasm to @ruvector/math-wasm - Update README with correct scoped package name - Update workflow to publish with scoped name - Add scripts/test-wasm.mjs for WASM package testing - Consistent with @ruvector/attention-* naming convention Published: - @ruvector/math-wasm@0.1.31 on npm Co-Authored-By: Claude Opus 4.5 --- .github/workflows/publish-all.yml | 10 +- crates/ruvector-math-wasm/README.md | 12 +- scripts/test-wasm.mjs | 176 ++++++++++++++++++++++++++++ 3 files changed, 188 insertions(+), 10 deletions(-) create mode 100755 scripts/test-wasm.mjs diff --git a/.github/workflows/publish-all.yml b/.github/workflows/publish-all.yml index b26f9876..3012e1a4 100644 --- a/.github/workflows/publish-all.yml +++ b/.github/workflows/publish-all.yml @@ -258,9 +258,11 @@ jobs: run: find artifacts -type f -name "*.node" -o -name "*.wasm" | head -50 # --- Publish WASM packages --- - - name: Publish ruvector-math-wasm to npm + - name: Publish @ruvector/math-wasm to npm run: | cd artifacts/wasm-ruvector-math-wasm + # Update package name to scoped + jq '.name = "@ruvector/math-wasm"' package.json > tmp.json && mv tmp.json package.json npm publish --access public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} @@ -462,7 +464,7 @@ jobs: run: | echo "## npm Publishing" >> $GITHUB_STEP_SUMMARY echo "### WASM Packages" >> $GITHUB_STEP_SUMMARY - echo "โœ… ruvector-math-wasm" >> $GITHUB_STEP_SUMMARY + echo "โœ… @ruvector/math-wasm" >> $GITHUB_STEP_SUMMARY echo "โœ… @ruvector/attention-wasm" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### Native Packages" >> $GITHUB_STEP_SUMMARY @@ -528,7 +530,7 @@ jobs: - `ruvector-attention-wasm` - WASM bindings for attention #### npm - - `ruvector-math-wasm` - Browser WASM package + - `@ruvector/math-wasm` - Browser WASM package - `@ruvector/attention` - Main Node.js package (auto-selects platform) - `@ruvector/attention-wasm` - Browser WASM package - Platform-specific: linux-x64, linux-arm64, darwin-x64, darwin-arm64, win32-x64 @@ -543,7 +545,7 @@ jobs: npm install @ruvector/attention # Browser (WASM) - npm install ruvector-math-wasm @ruvector/attention-wasm + npm install @ruvector/math-wasm @ruvector/attention-wasm ``` files: release-assets/* draft: false diff --git a/crates/ruvector-math-wasm/README.md b/crates/ruvector-math-wasm/README.md index c5439ef0..6f844671 100644 --- a/crates/ruvector-math-wasm/README.md +++ b/crates/ruvector-math-wasm/README.md @@ -1,6 +1,6 @@ -# ruvector-math-wasm +# @ruvector/math-wasm -[![npm version](https://img.shields.io/npm/v/ruvector-math-wasm.svg)](https://www.npmjs.com/package/ruvector-math-wasm) +[![npm version](https://img.shields.io/npm/v/@ruvector/math-wasm.svg)](https://www.npmjs.com/package/@ruvector/math-wasm) [![crates.io](https://img.shields.io/crates/v/ruvector-math-wasm.svg)](https://crates.io/crates/ruvector-math-wasm) [![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](LICENSE) [![WASM](https://img.shields.io/badge/target-wasm32-orange.svg)](https://webassembly.org/) @@ -21,7 +21,7 @@ Brings Optimal Transport, Information Geometry, and Product Manifolds to the bro ## Installation ```bash -npm install ruvector-math-wasm +npm install @ruvector/math-wasm # or yarn add ruvector-math-wasm # or @@ -37,7 +37,7 @@ import init, { WasmSlicedWasserstein, WasmSinkhorn, WasmProductManifold -} from 'ruvector-math-wasm'; +} from '@ruvector/math-wasm'; // Initialize WASM module await init(); @@ -53,7 +53,7 @@ console.log(`Wasserstein distance: ${distance}`); ### Node.js ```javascript -const { WasmSlicedWasserstein } = require('ruvector-math-wasm'); +const { WasmSlicedWasserstein } = require('@ruvector/math-wasm'); const sw = new WasmSlicedWasserstein(100); const dist = sw.distance(source, target, 2); @@ -163,7 +163,7 @@ Benchmarked on M1 MacBook Pro (WASM in Chrome): Full TypeScript definitions are included: ```typescript -import { WasmSlicedWasserstein, WasmSinkhornConfig } from 'ruvector-math-wasm'; +import { WasmSlicedWasserstein, WasmSinkhornConfig } from '@ruvector/math-wasm'; const sw: WasmSlicedWasserstein = new WasmSlicedWasserstein(100); const distance: number = sw.distance(source, target, dim); diff --git a/scripts/test-wasm.mjs b/scripts/test-wasm.mjs new file mode 100755 index 00000000..bc6f973d --- /dev/null +++ b/scripts/test-wasm.mjs @@ -0,0 +1,176 @@ +#!/usr/bin/env node +/** + * WASM Package Test Script + * Tests ruvector-math-wasm and ruvector-attention-wasm in Node.js + */ + +import { readFileSync } from 'fs'; +import { dirname, join } from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +console.log('๐Ÿงช Testing RuVector WASM Packages\n'); + +// ============================================================================ +// Test ruvector-math-wasm +// ============================================================================ +async function testMathWasm() { + console.log('๐Ÿ“ฆ Testing ruvector-math-wasm...'); + + const pkgPath = join(__dirname, '../crates/ruvector-math-wasm/pkg'); + + try { + // Load WASM module + const wasmPath = join(pkgPath, 'ruvector_math_wasm_bg.wasm'); + const wasmBuffer = readFileSync(wasmPath); + + // Import the JS bindings + const mathWasm = await import(join(pkgPath, 'ruvector_math_wasm.js')); + + // Initialize with WASM bytes + await mathWasm.default(wasmBuffer); + + // Test 1: Sliced Wasserstein Distance + console.log(' โ”œโ”€ Testing SlicedWasserstein...'); + const sw = new mathWasm.WasmSlicedWasserstein(100); + + // Create test point clouds (3 points in 2D each) + const source = new Float64Array([0, 0, 1, 0, 0, 1]); + const target = new Float64Array([2, 0, 3, 0, 2, 1]); + + const distance = sw.distance(source, target, 2); + console.log(` โ”‚ Distance: ${distance.toFixed(4)}`); + + if (distance > 0 && distance < 10) { + console.log(' โ”‚ โœ… SlicedWasserstein works!'); + } else { + throw new Error(`Unexpected distance: ${distance}`); + } + + // Test 2: Product Manifold + console.log(' โ”œโ”€ Testing ProductManifold...'); + const manifold = new mathWasm.WasmProductManifold(4, 2, 2); // E^4 x H^2 x S^2 + + // Create test points (8D total) + const pointA = new Float64Array([1, 0, 0, 0, 0.1, 0.1, 1, 0]); + const pointB = new Float64Array([0, 1, 0, 0, 0.2, 0.1, 0, 1]); + + const manifoldDist = manifold.distance(pointA, pointB); + console.log(` โ”‚ Manifold distance: ${manifoldDist.toFixed(4)}`); + + if (manifoldDist > 0) { + console.log(' โ”‚ โœ… ProductManifold works!'); + } else { + throw new Error(`Unexpected manifold distance: ${manifoldDist}`); + } + + // Test 3: Spherical Space + console.log(' โ”œโ”€ Testing SphericalSpace...'); + const sphere = new mathWasm.WasmSphericalSpace(3); + + const vecA = new Float64Array([1, 0, 0]); + const vecB = new Float64Array([0, 1, 0]); + + const sphereDist = sphere.distance(vecA, vecB); + console.log(` โ”‚ Spherical distance: ${sphereDist.toFixed(4)} (expected: ~1.5708 = ฯ€/2)`); + + if (Math.abs(sphereDist - Math.PI/2) < 0.01) { + console.log(' โ”‚ โœ… SphericalSpace works!'); + } else { + throw new Error(`Unexpected spherical distance: ${sphereDist}`); + } + + console.log(' โ””โ”€ โœ… ruvector-math-wasm: All tests passed!\n'); + return true; + + } catch (error) { + console.error(' โ””โ”€ โŒ Error:', error.message); + return false; + } +} + +// ============================================================================ +// Test ruvector-attention-wasm +// ============================================================================ +async function testAttentionWasm() { + console.log('๐Ÿ“ฆ Testing ruvector-attention-wasm...'); + + const pkgPath = join(__dirname, '../crates/ruvector-attention-wasm/pkg'); + + try { + // Check if pkg exists (need to build first) + const wasmPath = join(pkgPath, 'ruvector_attention_wasm_bg.wasm'); + + let wasmBuffer; + try { + wasmBuffer = readFileSync(wasmPath); + } catch { + console.log(' โ””โ”€ โš ๏ธ Package not built. Building now...'); + const { execSync } = await import('child_process'); + execSync('wasm-pack build crates/ruvector-attention-wasm --target web --release', { + cwd: join(__dirname, '..'), + stdio: 'inherit' + }); + wasmBuffer = readFileSync(wasmPath); + } + + // Import the JS bindings + const attentionWasm = await import(join(pkgPath, 'ruvector_attention_wasm.js')); + + // Initialize with WASM bytes + await attentionWasm.default(wasmBuffer); + + // Test 1: Scaled Dot Product Attention + console.log(' โ”œโ”€ Testing ScaledDotProductAttention...'); + + if (attentionWasm.WasmScaledDotProductAttention) { + const attention = new attentionWasm.WasmScaledDotProductAttention(64); + console.log(' โ”‚ โœ… ScaledDotProductAttention initialized'); + } else { + console.log(' โ”‚ โš ๏ธ ScaledDotProductAttention not exported'); + } + + // Test 2: Flash Attention (if available) + console.log(' โ”œโ”€ Testing FlashAttention...'); + + if (attentionWasm.WasmFlashAttention) { + const flash = new attentionWasm.WasmFlashAttention(64, 64); + console.log(' โ”‚ โœ… FlashAttention initialized'); + } else { + console.log(' โ”‚ โš ๏ธ FlashAttention not exported'); + } + + // List available exports + console.log(' โ”œโ”€ Available exports:'); + const exports = Object.keys(attentionWasm).filter(k => k.startsWith('Wasm')); + exports.forEach(e => console.log(` โ”‚ - ${e}`)); + + console.log(' โ””โ”€ โœ… ruvector-attention-wasm: Package loaded successfully!\n'); + return true; + + } catch (error) { + console.error(' โ””โ”€ โŒ Error:', error.message); + return false; + } +} + +// ============================================================================ +// Run all tests +// ============================================================================ +async function main() { + const results = { + math: await testMathWasm(), + attention: await testAttentionWasm() + }; + + console.log('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•'); + console.log('๐Ÿ“Š Test Results:'); + console.log(` ruvector-math-wasm: ${results.math ? 'โœ… PASS' : 'โŒ FAIL'}`); + console.log(` ruvector-attention-wasm: ${results.attention ? 'โœ… PASS' : 'โŒ FAIL'}`); + console.log('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•'); + + process.exit(results.math && results.attention ? 0 : 1); +} + +main().catch(console.error);