-
Notifications
You must be signed in to change notification settings - Fork 52
MLE-26340 expose vec.precision and vec.trunc in Node client. #1046
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,12 @@ | ||
| /* | ||
| * Copyright (c) 2015-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. | ||
| * Copyright (c) 2015-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. | ||
| */ | ||
| 'use strict'; | ||
|
|
||
| const should = require('should'); | ||
|
|
||
| const marklogic = require('../'); | ||
| const p = marklogic.planBuilder; | ||
| const op = marklogic.planBuilder; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❤️ |
||
|
|
||
| const pbb = require('./plan-builder-base'); | ||
| const execPlan = pbb.execPlan; | ||
|
|
@@ -34,129 +34,189 @@ describe('tests for new vector functions.', function() { | |
| }); | ||
|
|
||
| it('vec.add', function(done) { | ||
| const vec1 = p.vec.vector([0.000000000001]); | ||
| const vec2 = p.vec.vector([0.000000000001, 0.000000000002]); | ||
| testPlan([""],p.vec.add(p.vec.subvector(vec1,0),p.vec.subvector(vec2,1))) | ||
| const vec1 = op.vec.vector([1]); | ||
| const vec2 = op.vec.vector([2]); | ||
| testPlan([""],op.vec.add(vec1, vec2)) | ||
| .then(function(response) { | ||
| assert(response.rows[0].t.value[0][0] =='1e-12') | ||
| assert(response.rows[0].t.value[1][0]=='2e-12') | ||
| // add([1], [2]) = [3] (element-wise addition) | ||
| assert(response.rows[0].t.value[0] == 3, 'vec.add did not return expected value: [1] + [2] should be [3]'); | ||
| done(); | ||
| }).catch(error => done(error)); | ||
| }); | ||
|
|
||
| it('vec.subtract', function(done) { | ||
| const vec1 = p.vec.vector([0.000000000002]); | ||
| const vec2 = p.vec.vector([0.000000000001]); | ||
| testPlan([""],p.vec.subtract(p.vec.subvector(vec1,0),p.vec.subvector(vec2,0))) | ||
| const vec1 = op.vec.vector([2]); | ||
| const vec2 = op.vec.vector([1]); | ||
| testPlan([""],op.vec.subtract(vec1, vec2)) | ||
| .then(function(response) { | ||
| assert(response.rows[0].t.value[0][0] =='2e-12') | ||
| assert(response.rows[0].t.value[1][0]=='1e-12') | ||
| // vec.subtract([2], [1]) = [1] (element-wise subtraction) | ||
| assert(response.rows[0].t.value[0] == 1, 'vec.subtract did not return expected value: [2] - [1] should be [1]'); | ||
| done(); | ||
| }).catch(error => done(error)); | ||
| }); | ||
|
|
||
| it('vec.base64decode', function(done) { | ||
|
|
||
| const vec1 = p.vec.vector([0.002]); | ||
| testPlan([""],p.vec.subvector(p.vec.base64Decode(p.vec.base64Encode(p.vec.subvector(vec1,0))),0)) | ||
| it('vec.base64Decode', function(done) { | ||
| const vec1 = op.vec.vector([0.002]); | ||
| testPlan([""],op.vec.subvector(op.vec.base64Decode(op.vec.base64Encode(op.vec.subvector(vec1,0))),0)) | ||
| .then(function(response) { | ||
| assert(response.rows[0].t.value[0][0] =='0.002') | ||
| // Round-trip encode/decode returns [0.002] | ||
| assert(response.rows[0].t.value[0] == 0.002, 'vec.base64Decode did not return expected value for vector [0.002] after round-trip encode/decode'); | ||
| done(); | ||
| }).catch(error => done(error)); | ||
| }); | ||
|
|
||
| it('vec.base64Encode', function(done) { | ||
| const vec1 = p.vec.vector([0.002]); | ||
| testPlan([""],p.vec.base64Encode(p.vec.subvector(vec1,0))) | ||
| testPlan([""],op.vec.base64Encode(op.vec.vector([0.002]))) | ||
| .then(function(response) { | ||
| assert(response.rows[0].t.value =='AAAAAAEAAABvEgM7') | ||
| // qconsole shows that encoding vector vec.base64Encode([0.002]) returns 'AAAAAAEAAABvEgM7' | ||
| assert(response.rows[0].t.value =='AAAAAAEAAABvEgM7', 'vec.base64Encode did not return expected value for vector [0.002] which should be "AAAAAAEAAABvEgM7"'); | ||
| done(); | ||
| }).catch(error => done(error)); | ||
| }); | ||
|
|
||
| it('vec.cosine', function(done) { | ||
| const vec1 = p.vec.vector([1, 2, 3]) | ||
| const vec2 = p.vec.vector([4, 5, 6,7]) | ||
| // orthogonal vectors should have cosine similarity of 0, round down to 0 to be sure (floats) | ||
| const vec1 = op.vec.vector([1, 1]) | ||
| const vec2 = op.vec.vector([-1, 1]) | ||
|
|
||
| testPlan([""],p.vec.cosine(p.vec.subvector(vec1,0),p.vec.subvector(vec2,1))) | ||
| testPlan([""],op.math.floor(op.vec.cosine(vec1, vec2))) | ||
| .then(function(response) { | ||
| assert(response.rows[0].t.value != null); | ||
| assert(response.rows[0].t.value == 0, 'Cosine similarity between orthogonal vectors should be 0'); | ||
| }).catch(error => done(error)); | ||
|
|
||
| // cosine similarity between subvectors [1,2,3] and [5,6,7] should be approximately 0.968329 (to 6 decimal places) | ||
| testPlan([""],op.math.trunc(op.vec.cosine(op.vec.vector([1,2,3]),op.vec.vector([5,6,7])), 6)) | ||
| .then(function(response) { | ||
| assert(response.rows[0].t.value == '0.968329', 'Cosine (directional similarity) between vectors [1,2,3] and [5,6,7] should be approximately 0.968329'); | ||
| }).catch(error => done(error)); | ||
|
|
||
| // cosine similarity between subvectors [1,2,3] and [5,6,7] should be approximately 0.96832 | ||
| // and cosine distance should be approximately 0.03167 which is 1 - cosine similarity. | ||
| testPlan([""],op.vec.vector([ | ||
| op.math.trunc(op.vec.cosine(op.vec.vector([1,2,3]),op.vec.vector([5,6,7])), 5), | ||
| op.math.trunc(op.vec.cosineDistance(op.vec.vector([1,2,3]),op.vec.vector([5,6,7])), 5), | ||
| ])) | ||
| .then(function(response) { | ||
| assert(response.rows[0].t.value[0] == '0.96832', 'Cosine (directional similarity) between subvectors [1,2,3] and [5,6,7] should be approximately 0.96833.'); | ||
| assert(response.rows[0].t.value[1] == '0.03167', 'Cosine distance between subvectors [1,2,3] and [5,6,7] should be approximately 0.03167, or 1 - cosine similarity.'); | ||
| done(); | ||
| }).catch(error => done(error)); | ||
|
|
||
|
|
||
| }); | ||
|
|
||
| it('vec.dimension', function(done) { | ||
|
|
||
| testPlan([""],p.vec.dimension(p.vec.vector([1, 2, 3]))) | ||
| testPlan([""],op.vec.dimension(op.vec.vector([1, 2, 3]))) | ||
| .then(function(response) { | ||
| assert(response.rows[0].t.value == 3); | ||
| assert(response.rows[0].t.value == 3, 'Dimension of vector [1,2,3] should be 3'); | ||
| done(); | ||
| }).catch(error => done(error)); | ||
| }); | ||
|
|
||
| it('vec.dotproduct', function(done) { | ||
| const vec1 = p.vec.vector([1, 2, 3]) | ||
| const vec2 = p.vec.vector([4, 5, 6,7]) | ||
| it('vec.dotProduct', function(done) { | ||
| const vec1 = op.vec.vector([1, 2, 3]) | ||
| const vec2 = op.vec.vector([4, 5, 6, 7]) | ||
|
|
||
| testPlan([""],p.vec.cosine(p.vec.subvector(vec1,0),p.vec.subvector(vec2,1))) | ||
| // Dot product between subvectors [1,2,3] and [5,6,7] is (1*5) + (2*6) + (3*7) = 5 + 12 + 21 = 38 | ||
| testPlan([""],op.vec.dotProduct( | ||
| op.vec.subvector(vec1,0), | ||
| op.vec.subvector(vec2,1,3)) | ||
| ) | ||
| .then(function(response) { | ||
| assert(response.rows[0].t.value == '0.968329608440399'); | ||
| assert(response.rows[0].t.value == 38, 'Dot product between subvectors [1,2,3] and [5,6,7] should be 38'); | ||
| done(); | ||
| }).catch(error => done(error)); | ||
| }); | ||
|
|
||
| it('vec.euclideanDistance', function(done) { | ||
| const vec1 = p.vec.vector([1, 2, 3]) | ||
| const vec2 = p.vec.vector([4, 5, 6,7]) | ||
|
|
||
| testPlan([""],p.vec.euclideanDistance(p.vec.subvector(vec1,0,2),p.vec.subvector(vec2,1,2))) | ||
| const vec1 = op.vec.vector([1, 2, 3]) | ||
| const vec2 = op.vec.vector([4, 5, 6,7]) | ||
|
|
||
| // Euclidean distance between subvectors [1,2] and [5,6] is | ||
| // sqrt((1-5)^2 + (2-6)^2) = sqrt(16 + 16) = sqrt(32) | ||
| // This is approx 5.65685 | ||
| testPlan([""], op.math.trunc( | ||
| op.vec.euclideanDistance( | ||
| op.vec.vector([1,2]), | ||
| op.vec.vector([5,6])) | ||
| , 5)) | ||
| .then(function(response) { | ||
| assert(response.rows[0].t.value == '5.65685415267944'); | ||
| assert(response.rows[0].t.value == '5.65685', 'Euclidean distance between subvectors [1,2] and [5,6] should be approximately 5.65685, trunc() to 5 decimal places'); | ||
| done(); | ||
| }).catch(error => done(error)); | ||
| }); | ||
|
|
||
| it('vec.get', function(done) { | ||
|
|
||
| testPlan([""],p.vec.get(p.vec.vector([1, 2, 3]),1)) | ||
| testPlan([""],op.vec.get(op.vec.vector([1, 2, 3]),1)) | ||
| .then(function(response) { | ||
| assert(response.rows[0].t.value == 2); | ||
| assert(response.rows[0].t.value == 2, 'Element at index 1 of vector [1,2,3] should be 2'); | ||
| done(); | ||
| }).catch(error => done(error)); | ||
| }); | ||
|
|
||
| it('vec.magnitude', function(done) { | ||
|
|
||
| testPlan([""],p.vec.magnitude(p.vec.vector([1, 2, 3]))) | ||
| testPlan([""],op.math.trunc(op.vec.magnitude(op.vec.vector([1, 2, 3])), 5)) | ||
| .then(function(response) { | ||
| assert(response.rows[0].t.value == '3.74165749549866'); | ||
| // sqrt(1^2 + 2^2 + 3^2) = sqrt(14) ~= 3.74165 to 5 decimal places | ||
| assert(response.rows[0].t.value == '3.74165', 'Magnitude of [1,2,3] should be sqrt(1 + 4 + 9) ~= 3.74165 (trunc to 5 decimal places)'); | ||
| done(); | ||
| }).catch(error => done(error)); | ||
| }); | ||
|
|
||
| it('vec.normalize', function(done) { | ||
|
|
||
| testPlan([""],(p.vec.normalize(p.vec.subvector(p.vec.vector([1,2]),1)))) | ||
| testPlan([""],(op.vec.normalize(op.vec.vector([1,2])))) | ||
| .then(function(response) { | ||
| assert(response.rows[0].t.value == 2); | ||
| // normalize([1,2]) = [1/sqrt(5), 2/sqrt(5)] | ||
| // value[0] = 1/sqrt(5) ~= 0.447214 | ||
| // value[1] = 2/sqrt(5) ~= 0.894427 | ||
| assert(response.rows[0].t.value[0] == 0.447214, 'Normalized first element of vector [1,2] should be approximately 0.447214'); | ||
| assert(response.rows[0].t.value[1] == 0.894427, 'Normalized second element of vector [1,2] should be approximately 0.894427'); | ||
| done(); | ||
| }).catch(error => done(error)); | ||
| }); | ||
|
|
||
| it('vec.vectorScore', function(done) { | ||
| const vec1 = p.vec.vector([1, 2, 3]) | ||
| testPlan([""],(p.vec.vectorScore(24684,0.1,0.1))) | ||
| testPlan([""],(op.vec.vectorScore(24684,0.1,0.1))) | ||
| .then(function(response) { | ||
| assert(response.rows[0].t.value == 113124); | ||
| assert(response.rows[0].t.value == 113124, 'vectorScore(24684,0.1,0.1) should be 113124'); | ||
| done(); | ||
| }).catch(error => done(error)); | ||
| }); | ||
|
|
||
| it('vec.cosineDistance', function(done) { | ||
| testPlan([""],(p.vec.cosineDistance(p.vec.subvector(p.vec.vector([1, 2, 3]),0), | ||
| p.vec.subvector(p.vec.vector([4, 5, 6,7]),1)))) | ||
| // return a vector with two cosine distance calculations: one between identical direction vectors, | ||
| // one between opposite direction vectors | ||
| testPlan([""],(op.vec.vector([ | ||
| op.vec.cosineDistance(op.vec.vector([2, 2]), op.vec.vector([1, 1])), | ||
| op.vec.cosineDistance(op.vec.vector([2, 2]), op.vec.vector([-3, -3])) | ||
| ]))) | ||
| .then(function(response) { | ||
| assert(response.rows[0].t.value[0] == 0, 'Cosine distance should be 0 between identical direction vectors [2,2] and [1,1]'); | ||
| assert(response.rows[0].t.value[1] == 2, 'Cosine distance should be 2 between opposite direction vectors [2,2] and [-3,-3]'); | ||
| done(); | ||
| }).catch(error => done(error)); | ||
| }); | ||
|
|
||
| it('vec.precision', function(done) { | ||
| // return a vector with the values of pi, e, and sqrt(2) by truncation; we expect to see [3, 2, 1] | ||
| testPlan([""],op.vec.precision(op.vec.vector([3.14159265, 2.71828182, 1.41421356]), 10)) | ||
| .then(function(response) { | ||
| assert(response.rows[0].t.value != null); | ||
| assert(response.rows[0].t.value[0] == 3); | ||
| assert(response.rows[0].t.value[1] == 2); | ||
| assert(response.rows[0].t.value[2] == 1); | ||
| done(); | ||
| }).catch(error => done(error)); | ||
| }); | ||
|
|
||
| it('vec.trunc', function(done) { | ||
| // return a vector with the values of 1.123456789, 2.123456789, 3.123456789 truncated to 1 decimal place; | ||
| // we expect to see [1.1, 2.1, 3.1] | ||
| testPlan([""],(op.vec.trunc(op.vec.vector([1.123456789, 2.123456789, 3.123456789]), 1))) | ||
| .then(function(response) { | ||
| assert(response.rows[0].t.value == 0.0316703915596008); | ||
| assert(response.rows[0].t.value[0] == 1.1); | ||
| assert(response.rows[0].t.value[1] == 2.1); | ||
| assert(response.rows[0].t.value[2] == 3.1); | ||
| done(); | ||
| }).catch(error => done(error)); | ||
| }); | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.