@elarsaks/umap-wasm 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{src/heap.d.ts → heap.d.ts} +1 -1
- package/dist/heap.js +184 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/lib.d.ts +2 -0
- package/dist/lib.js +2 -0
- package/dist/matrix.js +257 -0
- package/dist/{src/nn_descent.d.ts → nn_descent.d.ts} +4 -4
- package/dist/nn_descent.js +127 -0
- package/dist/{src/tree.d.ts → tree.d.ts} +2 -2
- package/dist/tree.js +228 -0
- package/dist/{src/umap.d.ts → umap.d.ts} +1 -1
- package/dist/umap.js +700 -0
- package/dist/{src/utils.d.ts → utils.d.ts} +1 -1
- package/dist/utils.js +98 -0
- package/dist/wasmBridge.js +188 -0
- package/lib/umap-js.js +6842 -7490
- package/lib/umap-js.min.js +1 -1
- package/package.json +64 -63
- package/dist/src/heap.js +0 -226
- package/dist/src/index.d.ts +0 -2
- package/dist/src/index.js +0 -8
- package/dist/src/lib.d.ts +0 -1
- package/dist/src/lib.js +0 -5
- package/dist/src/matrix.js +0 -360
- package/dist/src/nn_descent.js +0 -204
- package/dist/src/tree.js +0 -320
- package/dist/src/umap.js +0 -842
- package/dist/src/utils.js +0 -137
- package/dist/src/wasmBridge.js +0 -290
- package/dist/test/matrix.test.d.ts +0 -1
- package/dist/test/matrix.test.js +0 -169
- package/dist/test/nn_descent.test.d.ts +0 -1
- package/dist/test/nn_descent.test.js +0 -58
- package/dist/test/smoke.playwright.test.d.ts +0 -1
- package/dist/test/smoke.playwright.test.js +0 -98
- package/dist/test/test_data.d.ts +0 -13
- package/dist/test/test_data.js +0 -1054
- package/dist/test/tree.test.d.ts +0 -1
- package/dist/test/tree.test.js +0 -60
- package/dist/test/umap.test.d.ts +0 -1
- package/dist/test/umap.test.js +0 -293
- package/dist/test/utils.test.d.ts +0 -1
- package/dist/test/utils.test.js +0 -128
- package/dist/test/wasmDistance.test.d.ts +0 -1
- package/dist/test/wasmDistance.test.js +0 -124
- package/dist/test/wasmMatrix.test.d.ts +0 -1
- package/dist/test/wasmMatrix.test.js +0 -389
- package/dist/test/wasmTree.test.d.ts +0 -1
- package/dist/test/wasmTree.test.js +0 -212
- /package/dist/{src/matrix.d.ts → matrix.d.ts} +0 -0
- /package/dist/{src/wasmBridge.d.ts → wasmBridge.d.ts} +0 -0
package/dist/tree.js
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import * as utils from './utils.js';
|
|
2
|
+
import { isWasmAvailable, buildRpTreeWasm, searchFlatTreeWasm, wasmTreeToJs } from './wasmBridge.js';
|
|
3
|
+
export class FlatTree {
|
|
4
|
+
constructor(hyperplanes, offsets, children, indices) {
|
|
5
|
+
this.hyperplanes = hyperplanes;
|
|
6
|
+
this.offsets = offsets;
|
|
7
|
+
this.children = children;
|
|
8
|
+
this.indices = indices;
|
|
9
|
+
}
|
|
10
|
+
static fromWasm(wasmTree) {
|
|
11
|
+
const jsData = wasmTreeToJs(wasmTree);
|
|
12
|
+
const tree = new FlatTree(jsData.hyperplanes, jsData.offsets, jsData.children, jsData.indices);
|
|
13
|
+
tree.wasmTree = wasmTree;
|
|
14
|
+
return tree;
|
|
15
|
+
}
|
|
16
|
+
getWasmTree() {
|
|
17
|
+
return this.wasmTree;
|
|
18
|
+
}
|
|
19
|
+
dispose() {
|
|
20
|
+
if (this.wasmTree) {
|
|
21
|
+
this.wasmTree.free();
|
|
22
|
+
this.wasmTree = undefined;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function makeForest(data, nNeighbors, nTrees, random, useWasm = false) {
|
|
27
|
+
const leafSize = Math.max(10, nNeighbors);
|
|
28
|
+
if (useWasm) {
|
|
29
|
+
if (!isWasmAvailable()) {
|
|
30
|
+
throw new Error('WASM requested but not available');
|
|
31
|
+
}
|
|
32
|
+
return makeForestWasm(data, leafSize, nTrees, random);
|
|
33
|
+
}
|
|
34
|
+
const trees = utils
|
|
35
|
+
.range(nTrees)
|
|
36
|
+
.map((_, i) => makeTree(data, leafSize, i, random));
|
|
37
|
+
const forest = trees.map(tree => flattenTree(tree, leafSize));
|
|
38
|
+
return forest;
|
|
39
|
+
}
|
|
40
|
+
function makeForestWasm(data, leafSize, nTrees, random) {
|
|
41
|
+
const nSamples = data.length;
|
|
42
|
+
const dim = data[0].length;
|
|
43
|
+
const forest = [];
|
|
44
|
+
for (let i = 0; i < nTrees; i++) {
|
|
45
|
+
const seed = Math.floor(random() * 0xFFFFFFFF);
|
|
46
|
+
const wasmTree = buildRpTreeWasm(data, nSamples, dim, leafSize, seed);
|
|
47
|
+
forest.push(FlatTree.fromWasm(wasmTree));
|
|
48
|
+
}
|
|
49
|
+
return forest;
|
|
50
|
+
}
|
|
51
|
+
function makeTree(data, leafSize = 30, n, random) {
|
|
52
|
+
const indices = utils.range(data.length);
|
|
53
|
+
const tree = makeEuclideanTree(data, indices, leafSize, n, random);
|
|
54
|
+
return tree;
|
|
55
|
+
}
|
|
56
|
+
function makeEuclideanTree(data, indices, leafSize = 30, q, random) {
|
|
57
|
+
if (indices.length > leafSize) {
|
|
58
|
+
const splitResults = euclideanRandomProjectionSplit(data, indices, random);
|
|
59
|
+
const { indicesLeft, indicesRight, hyperplane, offset } = splitResults;
|
|
60
|
+
const leftChild = makeEuclideanTree(data, indicesLeft, leafSize, q + 1, random);
|
|
61
|
+
const rightChild = makeEuclideanTree(data, indicesRight, leafSize, q + 1, random);
|
|
62
|
+
const node = { leftChild, rightChild, isLeaf: false, hyperplane, offset };
|
|
63
|
+
return node;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
const node = { indices, isLeaf: true };
|
|
67
|
+
return node;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function euclideanRandomProjectionSplit(data, indices, random) {
|
|
71
|
+
const dim = data[0].length;
|
|
72
|
+
let leftIndex = utils.tauRandInt(indices.length, random);
|
|
73
|
+
let rightIndex = utils.tauRandInt(indices.length, random);
|
|
74
|
+
rightIndex += leftIndex === rightIndex ? 1 : 0;
|
|
75
|
+
rightIndex = rightIndex % indices.length;
|
|
76
|
+
const left = indices[leftIndex];
|
|
77
|
+
const right = indices[rightIndex];
|
|
78
|
+
let hyperplaneOffset = 0;
|
|
79
|
+
const hyperplaneVector = utils.zeros(dim);
|
|
80
|
+
for (let i = 0; i < hyperplaneVector.length; i++) {
|
|
81
|
+
hyperplaneVector[i] = data[left][i] - data[right][i];
|
|
82
|
+
hyperplaneOffset -=
|
|
83
|
+
(hyperplaneVector[i] * (data[left][i] + data[right][i])) / 2.0;
|
|
84
|
+
}
|
|
85
|
+
let nLeft = 0;
|
|
86
|
+
let nRight = 0;
|
|
87
|
+
const side = utils.zeros(indices.length);
|
|
88
|
+
for (let i = 0; i < indices.length; i++) {
|
|
89
|
+
let margin = hyperplaneOffset;
|
|
90
|
+
for (let d = 0; d < dim; d++) {
|
|
91
|
+
margin += hyperplaneVector[d] * data[indices[i]][d];
|
|
92
|
+
}
|
|
93
|
+
if (margin === 0) {
|
|
94
|
+
side[i] = utils.tauRandInt(2, random);
|
|
95
|
+
if (side[i] === 0) {
|
|
96
|
+
nLeft += 1;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
nRight += 1;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else if (margin > 0) {
|
|
103
|
+
side[i] = 0;
|
|
104
|
+
nLeft += 1;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
side[i] = 1;
|
|
108
|
+
nRight += 1;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const indicesLeft = utils.zeros(nLeft);
|
|
112
|
+
const indicesRight = utils.zeros(nRight);
|
|
113
|
+
nLeft = 0;
|
|
114
|
+
nRight = 0;
|
|
115
|
+
for (let i = 0; i < side.length; i++) {
|
|
116
|
+
if (side[i] === 0) {
|
|
117
|
+
indicesLeft[nLeft] = indices[i];
|
|
118
|
+
nLeft += 1;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
indicesRight[nRight] = indices[i];
|
|
122
|
+
nRight += 1;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
indicesLeft,
|
|
127
|
+
indicesRight,
|
|
128
|
+
hyperplane: hyperplaneVector,
|
|
129
|
+
offset: hyperplaneOffset,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function flattenTree(tree, leafSize) {
|
|
133
|
+
const nNodes = numNodes(tree);
|
|
134
|
+
const nLeaves = numLeaves(tree);
|
|
135
|
+
const hyperplanes = utils
|
|
136
|
+
.range(nNodes)
|
|
137
|
+
.map(() => utils.zeros(tree.hyperplane ? tree.hyperplane.length : 0));
|
|
138
|
+
const offsets = utils.zeros(nNodes);
|
|
139
|
+
const children = utils.range(nNodes).map(() => [-1, -1]);
|
|
140
|
+
const indices = utils
|
|
141
|
+
.range(nLeaves)
|
|
142
|
+
.map(() => utils.range(leafSize).map(() => -1));
|
|
143
|
+
recursiveFlatten(tree, hyperplanes, offsets, children, indices, 0, 0);
|
|
144
|
+
return new FlatTree(hyperplanes, offsets, children, indices);
|
|
145
|
+
}
|
|
146
|
+
function recursiveFlatten(tree, hyperplanes, offsets, children, indices, nodeNum, leafNum) {
|
|
147
|
+
if (tree.isLeaf) {
|
|
148
|
+
children[nodeNum][0] = -leafNum;
|
|
149
|
+
indices[leafNum].splice(0, tree.indices.length, ...tree.indices);
|
|
150
|
+
leafNum += 1;
|
|
151
|
+
return { nodeNum, leafNum };
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
hyperplanes[nodeNum] = tree.hyperplane;
|
|
155
|
+
offsets[nodeNum] = tree.offset;
|
|
156
|
+
children[nodeNum][0] = nodeNum + 1;
|
|
157
|
+
const oldNodeNum = nodeNum;
|
|
158
|
+
let res = recursiveFlatten(tree.leftChild, hyperplanes, offsets, children, indices, nodeNum + 1, leafNum);
|
|
159
|
+
nodeNum = res.nodeNum;
|
|
160
|
+
leafNum = res.leafNum;
|
|
161
|
+
children[oldNodeNum][1] = nodeNum + 1;
|
|
162
|
+
res = recursiveFlatten(tree.rightChild, hyperplanes, offsets, children, indices, nodeNum + 1, leafNum);
|
|
163
|
+
return { nodeNum: res.nodeNum, leafNum: res.leafNum };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function numNodes(tree) {
|
|
167
|
+
if (tree.isLeaf) {
|
|
168
|
+
return 1;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
return 1 + numNodes(tree.leftChild) + numNodes(tree.rightChild);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function numLeaves(tree) {
|
|
175
|
+
if (tree.isLeaf) {
|
|
176
|
+
return 1;
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
return numLeaves(tree.leftChild) + numLeaves(tree.rightChild);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
export function makeLeafArray(rpForest) {
|
|
183
|
+
if (rpForest.length > 0) {
|
|
184
|
+
const output = [];
|
|
185
|
+
for (let tree of rpForest) {
|
|
186
|
+
output.push(...tree.indices);
|
|
187
|
+
}
|
|
188
|
+
return output;
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
return [[-1]];
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function selectSide(hyperplane, offset, point, random) {
|
|
195
|
+
let margin = offset;
|
|
196
|
+
for (let d = 0; d < point.length; d++) {
|
|
197
|
+
margin += hyperplane[d] * point[d];
|
|
198
|
+
}
|
|
199
|
+
if (margin === 0) {
|
|
200
|
+
const side = utils.tauRandInt(2, random);
|
|
201
|
+
return side;
|
|
202
|
+
}
|
|
203
|
+
else if (margin > 0) {
|
|
204
|
+
return 0;
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
return 1;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
export function searchFlatTree(point, tree, random) {
|
|
211
|
+
const wasmTree = tree.getWasmTree();
|
|
212
|
+
if (wasmTree && isWasmAvailable()) {
|
|
213
|
+
const seed = Math.floor(random() * 0xFFFFFFFF);
|
|
214
|
+
return searchFlatTreeWasm(wasmTree, point, seed);
|
|
215
|
+
}
|
|
216
|
+
let node = 0;
|
|
217
|
+
while (tree.children[node][0] > 0) {
|
|
218
|
+
const side = selectSide(tree.hyperplanes[node], tree.offsets[node], point, random);
|
|
219
|
+
if (side === 0) {
|
|
220
|
+
node = tree.children[node][0];
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
node = tree.children[node][1];
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const index = -1 * tree.children[node][0];
|
|
227
|
+
return tree.indices[index];
|
|
228
|
+
}
|