diff --git a/lib/vector-util.js b/lib/vector-util.js new file mode 100644 index 00000000..ae5619f5 --- /dev/null +++ b/lib/vector-util.js @@ -0,0 +1,44 @@ +/* +* Copyright © 2015-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. +*/ + +'use strict'; +const { Buffer } = require('buffer'); + +const base64Encode = (vector) => { + const dimensions = vector.length; + const buffer = Buffer.alloc(8 + 4 * dimensions); + + buffer.writeInt32LE(0, 0); + buffer.writeInt32LE(dimensions, 4); + + vector.forEach((value, i) => { + buffer.writeFloatLE(value, 8 + i * 4); + }); + + return buffer.toString('base64'); +}; + +const base64Decode = (encodedVector) => { + + const buffer = Buffer.from(encodedVector, 'base64'); + const version = buffer.readInt32LE(0); + + if (version !== 0) { + throw new Error(`Unsupported vector version: ${version}`); + } + + const dimensions = buffer.readInt32LE(4); + const vector = []; + + for (let i = 0; i < dimensions; i++) { + vector.push(buffer.readFloatLE(8 + i * 4)); + } + + return vector; +}; + +module.exports = { + base64Encode : base64Encode, + base64Decode: base64Decode +}; \ No newline at end of file diff --git a/test-basic/vector-util-test.js b/test-basic/vector-util-test.js new file mode 100644 index 00000000..a918628e --- /dev/null +++ b/test-basic/vector-util-test.js @@ -0,0 +1,86 @@ +/* +* Copyright © 2015-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. +*/ + +'use strict'; + +const assert = require('assert'); +const testlib = require('../etc/test-lib'); +let serverConfiguration = {}; +const vectorUtil = require('../lib/vector-util'); +const pbb = require("./plan-builder-base"); +const marklogic = require("../lib/marklogic"); +const testconfig = require("../etc/test-config"); +const vector = [3.14, 1.59, 2.65]; +const delta = 0.0001; +const p = marklogic.planBuilder; +const testPlan = pbb.testPlan; +const dbWriter = marklogic.createDatabaseClient(testconfig.restWriterConnection); + +describe('tests for vector-util', function () { + this.timeout(5000); + before(function (done) { + try { + testlib.findServerConfiguration(serverConfiguration); + setTimeout(() => { + if (serverConfiguration.serverVersion < 12) { + this.skip(); + } + done(); + }, 3000); + } catch (error) { + done(error); + } + }); + + it('should encode the vector correctly using client side', function (done) { + const encoded = vectorUtil.base64Encode(vector); + try{ + assert.strictEqual(encoded, 'AAAAAAMAAADD9UhAH4XLP5qZKUA='); + done(); + } catch(error){ + done(error); + } + }); + + it('should decode the vector correctly using client side without delta', function (done) { + const input = 'AAAAAAMAAADD9UhAH4XLP5qZKUA='; + const decoded = vectorUtil.base64Decode(input); + try { + assert(Array.isArray(decoded)); + assert.strictEqual(decoded[0], 3.140000104904175); + assert.strictEqual(decoded[1], 1.590000033378601); + assert.strictEqual(decoded[2], 2.6500000953674316); + for (let i = 0; i < vector.length; i++) { + assert(Math.abs(decoded[i] - vector[i]) < delta, `Value mismatch at index ${i}`); + } + done(); + } catch(error){ + done(error); + } + }); + + it('should encode using server-side vector function and decode using client side', function (done) { + const vec1 = [0.002]; + testPlan([""],p.vec.base64Encode(p.vec.subvector(p.vec.vector(vec1),0))) + .then(function(response) { + const input = response.rows[0].t.value; + const decoded = vectorUtil.base64Decode(input); + assert.strictEqual(input, 'AAAAAAEAAABvEgM7'); + assert(Array.isArray(decoded)); + assert.strictEqual(decoded[0], 0.0020000000949949026); + assert(Math.abs(decoded[0] - vec1[0]) < delta, `Value mismatch`); + done(); + }).catch(error => done(error)); + }); + + it('should encode using client-side vector function and decode using server side', done => { + + const input = vectorUtil.base64Encode(vector); + const vectorString = '[ '+vector[0].toString()+', '+vector[1].toString()+', '+vector[2].toString()+' ]'; + dbWriter.eval(`vec.base64Decode('${input}')`).result(res=>{ + assert.strictEqual(res[0].value, vectorString); + done(); + }).catch(error=> done(error)) + }); +}); \ No newline at end of file