|
1 | 1 | /* |
2 | | -* Copyright (c) 2015-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. |
| 2 | +* Copyright (c) 2015-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. |
3 | 3 | */ |
4 | 4 | 'use strict'; |
5 | 5 |
|
6 | 6 | const should = require('should'); |
7 | 7 |
|
8 | 8 | const marklogic = require('../'); |
9 | | -const p = marklogic.planBuilder; |
| 9 | +const op = marklogic.planBuilder; |
10 | 10 |
|
11 | 11 | const pbb = require('./plan-builder-base'); |
12 | 12 | const execPlan = pbb.execPlan; |
@@ -34,129 +34,189 @@ describe('tests for new vector functions.', function() { |
34 | 34 | }); |
35 | 35 |
|
36 | 36 | it('vec.add', function(done) { |
37 | | - const vec1 = p.vec.vector([0.000000000001]); |
38 | | - const vec2 = p.vec.vector([0.000000000001, 0.000000000002]); |
39 | | - testPlan([""],p.vec.add(p.vec.subvector(vec1,0),p.vec.subvector(vec2,1))) |
| 37 | + const vec1 = op.vec.vector([1]); |
| 38 | + const vec2 = op.vec.vector([2]); |
| 39 | + testPlan([""],op.vec.add(vec1, vec2)) |
40 | 40 | .then(function(response) { |
41 | | - assert(response.rows[0].t.value[0][0] =='1e-12') |
42 | | - assert(response.rows[0].t.value[1][0]=='2e-12') |
| 41 | + // add([1], [2]) = [3] (element-wise addition) |
| 42 | + assert(response.rows[0].t.value[0] == 3, 'vec.add did not return expected value: [1] + [2] should be [3]'); |
43 | 43 | done(); |
44 | 44 | }).catch(error => done(error)); |
45 | 45 | }); |
46 | 46 |
|
47 | 47 | it('vec.subtract', function(done) { |
48 | | - const vec1 = p.vec.vector([0.000000000002]); |
49 | | - const vec2 = p.vec.vector([0.000000000001]); |
50 | | - testPlan([""],p.vec.subtract(p.vec.subvector(vec1,0),p.vec.subvector(vec2,0))) |
| 48 | + const vec1 = op.vec.vector([2]); |
| 49 | + const vec2 = op.vec.vector([1]); |
| 50 | + testPlan([""],op.vec.subtract(vec1, vec2)) |
51 | 51 | .then(function(response) { |
52 | | - assert(response.rows[0].t.value[0][0] =='2e-12') |
53 | | - assert(response.rows[0].t.value[1][0]=='1e-12') |
| 52 | + // vec.subtract([2], [1]) = [1] (element-wise subtraction) |
| 53 | + assert(response.rows[0].t.value[0] == 1, 'vec.subtract did not return expected value: [2] - [1] should be [1]'); |
54 | 54 | done(); |
55 | 55 | }).catch(error => done(error)); |
56 | 56 | }); |
57 | 57 |
|
58 | | - it('vec.base64decode', function(done) { |
59 | | - |
60 | | - const vec1 = p.vec.vector([0.002]); |
61 | | - testPlan([""],p.vec.subvector(p.vec.base64Decode(p.vec.base64Encode(p.vec.subvector(vec1,0))),0)) |
| 58 | + it('vec.base64Decode', function(done) { |
| 59 | + const vec1 = op.vec.vector([0.002]); |
| 60 | + testPlan([""],op.vec.subvector(op.vec.base64Decode(op.vec.base64Encode(op.vec.subvector(vec1,0))),0)) |
62 | 61 | .then(function(response) { |
63 | | - assert(response.rows[0].t.value[0][0] =='0.002') |
| 62 | + // Round-trip encode/decode returns [0.002] |
| 63 | + 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'); |
64 | 64 | done(); |
65 | 65 | }).catch(error => done(error)); |
66 | 66 | }); |
67 | 67 |
|
68 | 68 | it('vec.base64Encode', function(done) { |
69 | | - const vec1 = p.vec.vector([0.002]); |
70 | | - testPlan([""],p.vec.base64Encode(p.vec.subvector(vec1,0))) |
| 69 | + testPlan([""],op.vec.base64Encode(op.vec.vector([0.002]))) |
71 | 70 | .then(function(response) { |
72 | | - assert(response.rows[0].t.value =='AAAAAAEAAABvEgM7') |
| 71 | + // qconsole shows that encoding vector vec.base64Encode([0.002]) returns 'AAAAAAEAAABvEgM7' |
| 72 | + assert(response.rows[0].t.value =='AAAAAAEAAABvEgM7', 'vec.base64Encode did not return expected value for vector [0.002] which should be "AAAAAAEAAABvEgM7"'); |
73 | 73 | done(); |
74 | 74 | }).catch(error => done(error)); |
75 | 75 | }); |
76 | 76 |
|
77 | 77 | it('vec.cosine', function(done) { |
78 | | - const vec1 = p.vec.vector([1, 2, 3]) |
79 | | - const vec2 = p.vec.vector([4, 5, 6,7]) |
| 78 | + // orthogonal vectors should have cosine similarity of 0, round down to 0 to be sure (floats) |
| 79 | + const vec1 = op.vec.vector([1, 1]) |
| 80 | + const vec2 = op.vec.vector([-1, 1]) |
80 | 81 |
|
81 | | - testPlan([""],p.vec.cosine(p.vec.subvector(vec1,0),p.vec.subvector(vec2,1))) |
| 82 | + testPlan([""],op.math.floor(op.vec.cosine(vec1, vec2))) |
82 | 83 | .then(function(response) { |
83 | | - assert(response.rows[0].t.value != null); |
| 84 | + assert(response.rows[0].t.value == 0, 'Cosine similarity between orthogonal vectors should be 0'); |
| 85 | + }).catch(error => done(error)); |
| 86 | + |
| 87 | + // cosine similarity between subvectors [1,2,3] and [5,6,7] should be approximately 0.968329 (to 6 decimal places) |
| 88 | + testPlan([""],op.math.trunc(op.vec.cosine(op.vec.vector([1,2,3]),op.vec.vector([5,6,7])), 6)) |
| 89 | + .then(function(response) { |
| 90 | + 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'); |
| 91 | + }).catch(error => done(error)); |
| 92 | + |
| 93 | + // cosine similarity between subvectors [1,2,3] and [5,6,7] should be approximately 0.96832 |
| 94 | + // and cosine distance should be approximately 0.03167 which is 1 - cosine similarity. |
| 95 | + testPlan([""],op.vec.vector([ |
| 96 | + op.math.trunc(op.vec.cosine(op.vec.vector([1,2,3]),op.vec.vector([5,6,7])), 5), |
| 97 | + op.math.trunc(op.vec.cosineDistance(op.vec.vector([1,2,3]),op.vec.vector([5,6,7])), 5), |
| 98 | + ])) |
| 99 | + .then(function(response) { |
| 100 | + 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.'); |
| 101 | + 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.'); |
84 | 102 | done(); |
85 | 103 | }).catch(error => done(error)); |
| 104 | + |
| 105 | + |
86 | 106 | }); |
87 | 107 |
|
88 | 108 | it('vec.dimension', function(done) { |
89 | | - |
90 | | - testPlan([""],p.vec.dimension(p.vec.vector([1, 2, 3]))) |
| 109 | + testPlan([""],op.vec.dimension(op.vec.vector([1, 2, 3]))) |
91 | 110 | .then(function(response) { |
92 | | - assert(response.rows[0].t.value == 3); |
| 111 | + assert(response.rows[0].t.value == 3, 'Dimension of vector [1,2,3] should be 3'); |
93 | 112 | done(); |
94 | 113 | }).catch(error => done(error)); |
95 | 114 | }); |
96 | 115 |
|
97 | | - it('vec.dotproduct', function(done) { |
98 | | - const vec1 = p.vec.vector([1, 2, 3]) |
99 | | - const vec2 = p.vec.vector([4, 5, 6,7]) |
| 116 | + it('vec.dotProduct', function(done) { |
| 117 | + const vec1 = op.vec.vector([1, 2, 3]) |
| 118 | + const vec2 = op.vec.vector([4, 5, 6, 7]) |
100 | 119 |
|
101 | | - testPlan([""],p.vec.cosine(p.vec.subvector(vec1,0),p.vec.subvector(vec2,1))) |
| 120 | + // Dot product between subvectors [1,2,3] and [5,6,7] is (1*5) + (2*6) + (3*7) = 5 + 12 + 21 = 38 |
| 121 | + testPlan([""],op.vec.dotProduct( |
| 122 | + op.vec.subvector(vec1,0), |
| 123 | + op.vec.subvector(vec2,1,3)) |
| 124 | + ) |
102 | 125 | .then(function(response) { |
103 | | - assert(response.rows[0].t.value == '0.968329608440399'); |
| 126 | + assert(response.rows[0].t.value == 38, 'Dot product between subvectors [1,2,3] and [5,6,7] should be 38'); |
104 | 127 | done(); |
105 | 128 | }).catch(error => done(error)); |
106 | 129 | }); |
107 | 130 |
|
108 | 131 | it('vec.euclideanDistance', function(done) { |
109 | | - const vec1 = p.vec.vector([1, 2, 3]) |
110 | | - const vec2 = p.vec.vector([4, 5, 6,7]) |
111 | | - |
112 | | - testPlan([""],p.vec.euclideanDistance(p.vec.subvector(vec1,0,2),p.vec.subvector(vec2,1,2))) |
| 132 | + const vec1 = op.vec.vector([1, 2, 3]) |
| 133 | + const vec2 = op.vec.vector([4, 5, 6,7]) |
| 134 | + |
| 135 | + // Euclidean distance between subvectors [1,2] and [5,6] is |
| 136 | + // sqrt((1-5)^2 + (2-6)^2) = sqrt(16 + 16) = sqrt(32) |
| 137 | + // This is approx 5.65685 |
| 138 | + testPlan([""], op.math.trunc( |
| 139 | + op.vec.euclideanDistance( |
| 140 | + op.vec.vector([1,2]), |
| 141 | + op.vec.vector([5,6])) |
| 142 | + , 5)) |
113 | 143 | .then(function(response) { |
114 | | - assert(response.rows[0].t.value == '5.65685415267944'); |
| 144 | + 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'); |
115 | 145 | done(); |
116 | 146 | }).catch(error => done(error)); |
117 | 147 | }); |
118 | 148 |
|
119 | 149 | it('vec.get', function(done) { |
120 | | - |
121 | | - testPlan([""],p.vec.get(p.vec.vector([1, 2, 3]),1)) |
| 150 | + testPlan([""],op.vec.get(op.vec.vector([1, 2, 3]),1)) |
122 | 151 | .then(function(response) { |
123 | | - assert(response.rows[0].t.value == 2); |
| 152 | + assert(response.rows[0].t.value == 2, 'Element at index 1 of vector [1,2,3] should be 2'); |
124 | 153 | done(); |
125 | 154 | }).catch(error => done(error)); |
126 | 155 | }); |
127 | 156 |
|
128 | 157 | it('vec.magnitude', function(done) { |
129 | | - |
130 | | - testPlan([""],p.vec.magnitude(p.vec.vector([1, 2, 3]))) |
| 158 | + testPlan([""],op.math.trunc(op.vec.magnitude(op.vec.vector([1, 2, 3])), 5)) |
131 | 159 | .then(function(response) { |
132 | | - assert(response.rows[0].t.value == '3.74165749549866'); |
| 160 | + // sqrt(1^2 + 2^2 + 3^2) = sqrt(14) ~= 3.74165 to 5 decimal places |
| 161 | + 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)'); |
133 | 162 | done(); |
134 | 163 | }).catch(error => done(error)); |
135 | 164 | }); |
136 | 165 |
|
137 | 166 | it('vec.normalize', function(done) { |
138 | | - |
139 | | - testPlan([""],(p.vec.normalize(p.vec.subvector(p.vec.vector([1,2]),1)))) |
| 167 | + testPlan([""],(op.vec.normalize(op.vec.vector([1,2])))) |
140 | 168 | .then(function(response) { |
141 | | - assert(response.rows[0].t.value == 2); |
| 169 | + // normalize([1,2]) = [1/sqrt(5), 2/sqrt(5)] |
| 170 | + // value[0] = 1/sqrt(5) ~= 0.447214 |
| 171 | + // value[1] = 2/sqrt(5) ~= 0.894427 |
| 172 | + assert(response.rows[0].t.value[0] == 0.447214, 'Normalized first element of vector [1,2] should be approximately 0.447214'); |
| 173 | + assert(response.rows[0].t.value[1] == 0.894427, 'Normalized second element of vector [1,2] should be approximately 0.894427'); |
142 | 174 | done(); |
143 | 175 | }).catch(error => done(error)); |
144 | 176 | }); |
145 | 177 |
|
146 | 178 | it('vec.vectorScore', function(done) { |
147 | | - const vec1 = p.vec.vector([1, 2, 3]) |
148 | | - testPlan([""],(p.vec.vectorScore(24684,0.1,0.1))) |
| 179 | + testPlan([""],(op.vec.vectorScore(24684,0.1,0.1))) |
149 | 180 | .then(function(response) { |
150 | | - assert(response.rows[0].t.value == 113124); |
| 181 | + assert(response.rows[0].t.value == 113124, 'vectorScore(24684,0.1,0.1) should be 113124'); |
151 | 182 | done(); |
152 | 183 | }).catch(error => done(error)); |
153 | 184 | }); |
154 | 185 |
|
155 | 186 | it('vec.cosineDistance', function(done) { |
156 | | - testPlan([""],(p.vec.cosineDistance(p.vec.subvector(p.vec.vector([1, 2, 3]),0), |
157 | | - p.vec.subvector(p.vec.vector([4, 5, 6,7]),1)))) |
| 187 | + // return a vector with two cosine distance calculations: one between identical direction vectors, |
| 188 | + // one between opposite direction vectors |
| 189 | + testPlan([""],(op.vec.vector([ |
| 190 | + op.vec.cosineDistance(op.vec.vector([2, 2]), op.vec.vector([1, 1])), |
| 191 | + op.vec.cosineDistance(op.vec.vector([2, 2]), op.vec.vector([-3, -3])) |
| 192 | + ]))) |
| 193 | + .then(function(response) { |
| 194 | + assert(response.rows[0].t.value[0] == 0, 'Cosine distance should be 0 between identical direction vectors [2,2] and [1,1]'); |
| 195 | + assert(response.rows[0].t.value[1] == 2, 'Cosine distance should be 2 between opposite direction vectors [2,2] and [-3,-3]'); |
| 196 | + done(); |
| 197 | + }).catch(error => done(error)); |
| 198 | + }); |
| 199 | + |
| 200 | + it('vec.precision', function(done) { |
| 201 | + // return a vector with the values of pi, e, and sqrt(2) by truncation; we expect to see [3, 2, 1] |
| 202 | + testPlan([""],op.vec.precision(op.vec.vector([3.14159265, 2.71828182, 1.41421356]), 10)) |
| 203 | + .then(function(response) { |
| 204 | + assert(response.rows[0].t.value != null); |
| 205 | + assert(response.rows[0].t.value[0] == 3); |
| 206 | + assert(response.rows[0].t.value[1] == 2); |
| 207 | + assert(response.rows[0].t.value[2] == 1); |
| 208 | + done(); |
| 209 | + }).catch(error => done(error)); |
| 210 | + }); |
| 211 | + |
| 212 | + it('vec.trunc', function(done) { |
| 213 | + // return a vector with the values of 1.123456789, 2.123456789, 3.123456789 truncated to 1 decimal place; |
| 214 | + // we expect to see [1.1, 2.1, 3.1] |
| 215 | + testPlan([""],(op.vec.trunc(op.vec.vector([1.123456789, 2.123456789, 3.123456789]), 1))) |
158 | 216 | .then(function(response) { |
159 | | - assert(response.rows[0].t.value == 0.0316703915596008); |
| 217 | + assert(response.rows[0].t.value[0] == 1.1); |
| 218 | + assert(response.rows[0].t.value[1] == 2.1); |
| 219 | + assert(response.rows[0].t.value[2] == 3.1); |
160 | 220 | done(); |
161 | 221 | }).catch(error => done(error)); |
162 | 222 | }); |
|
0 commit comments