@nshiab/simple-data-analysis-core 2.0.7 → 2.0.9
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/esm/class/SimpleTable.d.ts +72 -0
- package/esm/class/SimpleTable.d.ts.map +1 -1
- package/esm/class/SimpleTable.js +68 -0
- package/esm/helpers/appendColumnBatches.d.ts +3 -0
- package/esm/helpers/appendColumnBatches.d.ts.map +1 -0
- package/esm/helpers/appendColumnBatches.js +21 -0
- package/esm/helpers/buildUmapGraph.d.ts +24 -0
- package/esm/helpers/buildUmapGraph.d.ts.map +1 -0
- package/esm/helpers/buildUmapGraph.js +155 -0
- package/esm/helpers/cacheDatabase.d.ts.map +1 -1
- package/esm/helpers/cacheDatabase.js +2 -1
- package/esm/helpers/fitUmapCurve.d.ts +2 -0
- package/esm/helpers/fitUmapCurve.d.ts.map +1 -0
- package/esm/helpers/fitUmapCurve.js +66 -0
- package/esm/helpers/initialUmapCoordinates.d.ts +2 -0
- package/esm/helpers/initialUmapCoordinates.d.ts.map +1 -0
- package/esm/helpers/initialUmapCoordinates.js +5 -0
- package/esm/helpers/optimizeUmapLayout.d.ts +7 -0
- package/esm/helpers/optimizeUmapLayout.d.ts.map +1 -0
- package/esm/helpers/optimizeUmapLayout.js +93 -0
- package/esm/helpers/prepareUmapLayoutOptions.d.ts +14 -0
- package/esm/helpers/prepareUmapLayoutOptions.d.ts.map +1 -0
- package/esm/helpers/prepareUmapLayoutOptions.js +23 -0
- package/esm/helpers/readScalarNumber.d.ts +3 -0
- package/esm/helpers/readScalarNumber.d.ts.map +1 -0
- package/esm/helpers/readScalarNumber.js +4 -0
- package/esm/helpers/umapRandom.d.ts +2 -0
- package/esm/helpers/umapRandom.d.ts.map +1 -0
- package/esm/helpers/umapRandom.js +10 -0
- package/esm/methods/cache.js +1 -1
- package/esm/methods/loadArray.d.ts.map +1 -1
- package/esm/methods/loadArray.js +3 -20
- package/esm/methods/log.d.ts.map +1 -1
- package/esm/methods/log.js +1 -4
- package/esm/methods/umap.d.ts +5 -0
- package/esm/methods/umap.d.ts.map +1 -0
- package/esm/methods/umap.js +156 -0
- package/esm/methods/updateWithJS.js +9 -21
- package/llm.md +80 -1
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/script/class/SimpleTable.d.ts +72 -0
- package/script/class/SimpleTable.d.ts.map +1 -1
- package/script/class/SimpleTable.js +68 -0
- package/script/helpers/appendColumnBatches.d.ts +3 -0
- package/script/helpers/appendColumnBatches.d.ts.map +1 -0
- package/script/helpers/appendColumnBatches.js +24 -0
- package/script/helpers/buildUmapGraph.d.ts +24 -0
- package/script/helpers/buildUmapGraph.d.ts.map +1 -0
- package/script/helpers/buildUmapGraph.js +161 -0
- package/script/helpers/cacheDatabase.d.ts.map +1 -1
- package/script/helpers/cacheDatabase.js +2 -1
- package/script/helpers/fitUmapCurve.d.ts +2 -0
- package/script/helpers/fitUmapCurve.d.ts.map +1 -0
- package/script/helpers/fitUmapCurve.js +69 -0
- package/script/helpers/initialUmapCoordinates.d.ts +2 -0
- package/script/helpers/initialUmapCoordinates.d.ts.map +1 -0
- package/script/helpers/initialUmapCoordinates.js +11 -0
- package/script/helpers/optimizeUmapLayout.d.ts +7 -0
- package/script/helpers/optimizeUmapLayout.d.ts.map +1 -0
- package/script/helpers/optimizeUmapLayout.js +99 -0
- package/script/helpers/prepareUmapLayoutOptions.d.ts +14 -0
- package/script/helpers/prepareUmapLayoutOptions.d.ts.map +1 -0
- package/script/helpers/prepareUmapLayoutOptions.js +29 -0
- package/script/helpers/readScalarNumber.d.ts +3 -0
- package/script/helpers/readScalarNumber.d.ts.map +1 -0
- package/script/helpers/readScalarNumber.js +7 -0
- package/script/helpers/umapRandom.d.ts +2 -0
- package/script/helpers/umapRandom.d.ts.map +1 -0
- package/script/helpers/umapRandom.js +13 -0
- package/script/methods/cache.js +1 -1
- package/script/methods/loadArray.d.ts.map +1 -1
- package/script/methods/loadArray.js +2 -19
- package/script/methods/log.d.ts.map +1 -1
- package/script/methods/log.js +1 -4
- package/script/methods/umap.d.ts +5 -0
- package/script/methods/umap.d.ts.map +1 -0
- package/script/methods/umap.js +162 -0
- package/script/methods/updateWithJS.js +9 -21
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = initialUmapCoordinates;
|
|
7
|
+
const umapRandom_js_1 = __importDefault(require("./umapRandom.js"));
|
|
8
|
+
function initialUmapCoordinates(count, seed) {
|
|
9
|
+
const random = (0, umapRandom_js_1.default)(seed);
|
|
10
|
+
return Float64Array.from({ length: count * 2 }, () => random() * 20 - 10);
|
|
11
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type prepareUmapLayoutOptions from "./prepareUmapLayoutOptions.js";
|
|
2
|
+
export default function optimizeUmapLayout(graph: {
|
|
3
|
+
source: Uint32Array;
|
|
4
|
+
target: Uint32Array;
|
|
5
|
+
weight: Float64Array;
|
|
6
|
+
}, initial: Float64Array, options: ReturnType<typeof prepareUmapLayoutOptions>): Float64Array;
|
|
7
|
+
//# sourceMappingURL=optimizeUmapLayout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"optimizeUmapLayout.d.ts","sourceRoot":"","sources":["../../src/helpers/optimizeUmapLayout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,wBAAwB,MAAM,+BAA+B,CAAC;AAS1E,MAAM,CAAC,OAAO,UAAU,kBAAkB,CACxC,KAAK,EAAE;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,EACzE,OAAO,EAAE,YAAY,EACrB,OAAO,EAAE,UAAU,CAAC,OAAO,wBAAwB,CAAC,GACnD,YAAY,CA2Fd"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = optimizeUmapLayout;
|
|
7
|
+
const umapRandom_js_1 = __importDefault(require("./umapRandom.js"));
|
|
8
|
+
// Internal interface. Graph entries are directed, in a stable order; the fuzzy
|
|
9
|
+
// union graph supplies both orientations. Coordinates are interleaved x,y.
|
|
10
|
+
// Inputs are borrowed read-only. State is O(edges + vertices), never O(n²).
|
|
11
|
+
// Implements reference Euclidean UMAP SGD: scheduled attractive edges, sampled
|
|
12
|
+
// repulsion, clipped gradients, symmetric attractive updates, linear decay.
|
|
13
|
+
// Algorithm: https://umap-learn.readthedocs.io/en/latest/how_umap_works.html
|
|
14
|
+
function optimizeUmapLayout(graph, initial, options) {
|
|
15
|
+
const { epochs, seed, learningRate, negativeSamples, curve: [a, b] } = options;
|
|
16
|
+
const count = initial.length / 2;
|
|
17
|
+
if (!Number.isInteger(count) || count < 3 || count > 0xffffffff) {
|
|
18
|
+
throw new Error("Expected interleaved coordinates for at least three vertices.");
|
|
19
|
+
}
|
|
20
|
+
for (const value of initial) {
|
|
21
|
+
if (!Number.isFinite(value)) {
|
|
22
|
+
throw new Error("Initial coordinates must be finite.");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const { source, target, weight } = graph;
|
|
26
|
+
if (!weight.length || source.length !== weight.length ||
|
|
27
|
+
target.length !== weight.length)
|
|
28
|
+
throw new Error("Invalid graph column lengths.");
|
|
29
|
+
let maximum = 0;
|
|
30
|
+
for (let edge = 0; edge < weight.length; edge++) {
|
|
31
|
+
if (source[edge] >= count || target[edge] >= count ||
|
|
32
|
+
source[edge] === target[edge])
|
|
33
|
+
throw new Error("Graph contains an invalid vertex or self edge.");
|
|
34
|
+
if (!Number.isFinite(weight[edge]) || weight[edge] < 0 || weight[edge] > 1)
|
|
35
|
+
throw new Error("Graph weights must be between zero and one.");
|
|
36
|
+
maximum = Math.max(maximum, weight[edge]);
|
|
37
|
+
}
|
|
38
|
+
if (!maximum)
|
|
39
|
+
throw new Error("Graph must contain a positive edge.");
|
|
40
|
+
const period = new Float64Array(weight.length);
|
|
41
|
+
const nextPositive = new Float64Array(weight.length);
|
|
42
|
+
const nextNegative = new Float64Array(weight.length);
|
|
43
|
+
// Reference pruning uses 500 when explicitly requesting at most ten epochs.
|
|
44
|
+
const threshold = maximum / (epochs > 10 ? epochs : 500);
|
|
45
|
+
for (let edge = 0; edge < weight.length; edge++) {
|
|
46
|
+
period[edge] = weight[edge] > 0 && weight[edge] >= threshold
|
|
47
|
+
? epochs / ((weight[edge] / maximum) * epochs)
|
|
48
|
+
: Infinity;
|
|
49
|
+
nextPositive[edge] = period[edge];
|
|
50
|
+
nextNegative[edge] = period[edge] / negativeSamples;
|
|
51
|
+
}
|
|
52
|
+
const coordinates = initial.slice();
|
|
53
|
+
const random = (0, umapRandom_js_1.default)(seed ^ 0x7f4a7c15);
|
|
54
|
+
const clip = (gradient) => Math.max(-4, Math.min(4, gradient));
|
|
55
|
+
let alpha = learningRate;
|
|
56
|
+
for (let epoch = 0; epoch < epochs; epoch++) {
|
|
57
|
+
for (let edge = 0; edge < weight.length; edge++) {
|
|
58
|
+
if (nextPositive[edge] > epoch)
|
|
59
|
+
continue;
|
|
60
|
+
const u = source[edge] * 2, v = target[edge] * 2;
|
|
61
|
+
let dx = coordinates[u] - coordinates[v], dy = coordinates[u + 1] - coordinates[v + 1];
|
|
62
|
+
let squared = dx * dx + dy * dy;
|
|
63
|
+
if (squared > 0) {
|
|
64
|
+
const attraction = -2 * a * b * Math.pow(squared, b - 1) /
|
|
65
|
+
(1 + a * Math.pow(squared, b));
|
|
66
|
+
const gx = clip(attraction * dx) * alpha, gy = clip(attraction * dy) * alpha;
|
|
67
|
+
coordinates[u] += gx;
|
|
68
|
+
coordinates[u + 1] += gy;
|
|
69
|
+
coordinates[v] -= gx;
|
|
70
|
+
coordinates[v + 1] -= gy;
|
|
71
|
+
}
|
|
72
|
+
nextPositive[edge] += period[edge];
|
|
73
|
+
const negativePeriod = period[edge] / negativeSamples;
|
|
74
|
+
const draws = Math.floor((epoch - nextNegative[edge]) / negativePeriod);
|
|
75
|
+
for (let draw = 0; draw < draws; draw++) {
|
|
76
|
+
const other = Math.floor(random() * count) * 2;
|
|
77
|
+
dx = coordinates[u] - coordinates[other];
|
|
78
|
+
dy = coordinates[u + 1] - coordinates[other + 1];
|
|
79
|
+
squared = dx * dx + dy * dy;
|
|
80
|
+
// Coincident points get zero repulsive gradient, as in current Python
|
|
81
|
+
// UMAP; this intentionally differs from UMAP-JS's constant +4 fallback.
|
|
82
|
+
if (squared > 0) {
|
|
83
|
+
const repulsion = 2 * b /
|
|
84
|
+
((0.001 + squared) * (1 + a * Math.pow(squared, b)));
|
|
85
|
+
coordinates[u] += clip(repulsion * dx) * alpha;
|
|
86
|
+
coordinates[u + 1] += clip(repulsion * dy) * alpha;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
nextNegative[edge] += draws * negativePeriod;
|
|
90
|
+
}
|
|
91
|
+
alpha = learningRate * (1 - epoch / epochs);
|
|
92
|
+
}
|
|
93
|
+
for (const value of coordinates) {
|
|
94
|
+
if (!Number.isFinite(value)) {
|
|
95
|
+
throw new Error("UMAP optimization produced a non-finite coordinate.");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return coordinates;
|
|
99
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export default function prepareUmapLayoutOptions(options?: {
|
|
2
|
+
epochs?: number;
|
|
3
|
+
seed?: number;
|
|
4
|
+
minDistance?: number;
|
|
5
|
+
learningRate?: number;
|
|
6
|
+
negativeSamples?: number;
|
|
7
|
+
}): {
|
|
8
|
+
epochs: number;
|
|
9
|
+
seed: number;
|
|
10
|
+
learningRate: number;
|
|
11
|
+
negativeSamples: number;
|
|
12
|
+
curve: [number, number];
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=prepareUmapLayoutOptions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prepareUmapLayoutOptions.d.ts","sourceRoot":"","sources":["../../src/helpers/prepareUmapLayoutOptions.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,OAAO,UAAU,wBAAwB,CAC9C,OAAO,GAAE;IACP,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;CACrB;;;;;;EA2BP"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = prepareUmapLayoutOptions;
|
|
7
|
+
const fitUmapCurve_js_1 = __importDefault(require("./fitUmapCurve.js"));
|
|
8
|
+
// Resolve and validate settings before database work, fitting the curve once.
|
|
9
|
+
function prepareUmapLayoutOptions(options = {}) {
|
|
10
|
+
const { epochs = 200, seed = 42, minDistance = 0.1, learningRate = 1, negativeSamples = 5, } = options;
|
|
11
|
+
for (const [key, value] of Object.entries({ epochs, negativeSamples })) {
|
|
12
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
13
|
+
throw new Error(`${key} must be a positive safe integer.`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
if (!Number.isInteger(seed) || seed < 0 || seed > 0xffffffff) {
|
|
17
|
+
throw new Error("seed must be an integer between 0 and 4294967295.");
|
|
18
|
+
}
|
|
19
|
+
if (!Number.isFinite(learningRate) || learningRate <= 0) {
|
|
20
|
+
throw new Error("learningRate must be finite and positive.");
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
epochs,
|
|
24
|
+
seed,
|
|
25
|
+
learningRate,
|
|
26
|
+
negativeSamples,
|
|
27
|
+
curve: (0, fitUmapCurve_js_1.default)(minDistance),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readScalarNumber.d.ts","sourceRoot":"","sources":["../../src/helpers/readScalarNumber.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGzD,wBAA8B,gBAAgB,CAC5C,UAAU,EAAE,gBAAgB,EAC5B,GAAG,EAAE,MAAM,mBAGZ"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = readScalarNumber;
|
|
4
|
+
// For internal queries returning a single numeric value, such as row counts.
|
|
5
|
+
async function readScalarNumber(connection, sql) {
|
|
6
|
+
return Number((await connection.runAndReadAll(sql)).getRowsJS()[0][0]);
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"umapRandom.d.ts","sourceRoot":"","sources":["../../src/helpers/umapRandom.ts"],"names":[],"mappings":"AACA,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,IAAI,EAAE,MAAM,gBAQ9C"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = umapRandom;
|
|
4
|
+
// Mulberry32: separate initialization and sampling streams, stable per seed.
|
|
5
|
+
function umapRandom(seed) {
|
|
6
|
+
let state = seed >>> 0;
|
|
7
|
+
return () => {
|
|
8
|
+
state = (state + 0x6D2B79F5) | 0;
|
|
9
|
+
let value = Math.imul(state ^ state >>> 15, 1 | state);
|
|
10
|
+
value ^= value + Math.imul(value ^ value >>> 7, 61 | value);
|
|
11
|
+
return ((value ^ value >>> 14) >>> 0) / 4294967296;
|
|
12
|
+
};
|
|
13
|
+
}
|
package/script/methods/cache.js
CHANGED
|
@@ -46,7 +46,7 @@ const serializeCacheInputs_js_1 = __importStar(require("../helpers/serializeCach
|
|
|
46
46
|
const tableGeneration_js_1 = require("../helpers/tableGeneration.js");
|
|
47
47
|
const cacheDatabase_js_1 = require("../helpers/cacheDatabase.js");
|
|
48
48
|
const cacheTableDependencies_js_1 = require("../helpers/cacheTableDependencies.js");
|
|
49
|
-
const CACHE_FORMAT_VERSION = "duckdb-
|
|
49
|
+
const CACHE_FORMAT_VERSION = "duckdb-v5";
|
|
50
50
|
async function cache(table, compute, options = {}) {
|
|
51
51
|
// If cache() is itself called from another cache computation, the outer
|
|
52
52
|
// computation depends on the table produced here.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadArray.d.ts","sourceRoot":"","sources":["../../src/methods/loadArray.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"loadArray.d.ts","sourceRoot":"","sources":["../../src/methods/loadArray.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,WAAW,EACjB,MAAM,kBAAkB,CAAC;AAM1B,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AAKvD,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,WAAW,EAAE,WAAW,EACxB,IAAI,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,EAAE,EAClC,OAAO,GAAE,WAAW,CAAC,UAAU,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAM,QAiBnE;AAED,KAAK,aAAa,GAAG;IACnB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,WAAW,EAAE,WAAW,EAAE,EAAE,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,wBAAgB,YAAY,CAC1B,IAAI,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,EAAE,EAClC,WAAW,GAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAO,EAC3C,0BAA0B,UAAQ,GACjC,aAAa,CAsHf;AAED,wBAAsB,oBAAoB,CACxC,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,aAAa,iBA+DxB"}
|
|
@@ -7,6 +7,7 @@ exports.default = loadArray;
|
|
|
7
7
|
exports.prepareArray = prepareArray;
|
|
8
8
|
exports.executePreparedArray = executePreparedArray;
|
|
9
9
|
const node_api_1 = require("@duckdb/node-api");
|
|
10
|
+
const appendColumnBatches_js_1 = __importDefault(require("../helpers/appendColumnBatches.js"));
|
|
10
11
|
const prepareGeometry_js_1 = __importDefault(require("../helpers/prepareGeometry.js"));
|
|
11
12
|
const prepareJSON_js_1 = __importDefault(require("../helpers/prepareJSON.js"));
|
|
12
13
|
const geometryFromJSON_js_1 = __importDefault(require("../helpers/geometryFromJSON.js"));
|
|
@@ -144,25 +145,7 @@ async function executePreparedArray(simpleTable, prepared) {
|
|
|
144
145
|
}
|
|
145
146
|
try {
|
|
146
147
|
await simpleTable.sdb.customQuery(`CREATE ${needsConversion ? "TEMP" : "OR REPLACE"} TABLE ${(0, quoteIdentifier_js_1.default)(staged)}(${keys.map((key, i) => `${(0, quoteIdentifier_js_1.default)(key)} ${storageTypes[i]}`).join(", ")})`);
|
|
147
|
-
|
|
148
|
-
.createAppender(staged);
|
|
149
|
-
try {
|
|
150
|
-
const duckDBTypes = storageTypes.map((d) => (0, parseDuckDBType_js_1.default)(d));
|
|
151
|
-
// The maximum capacity of a DuckDB data chunk is 2048 rows.
|
|
152
|
-
const chunkSize = 2000;
|
|
153
|
-
for (let start = 0; start < rowCount; start += chunkSize) {
|
|
154
|
-
const end = Math.min(start + chunkSize, rowCount);
|
|
155
|
-
const dataChunk = node_api_1.DuckDBDataChunk.create(duckDBTypes, end - start);
|
|
156
|
-
for (let i = 0; i < keys.length; i++) {
|
|
157
|
-
dataChunk.setColumnValues(i, columnsData[i].slice(start, end));
|
|
158
|
-
}
|
|
159
|
-
appender.appendDataChunk(dataChunk);
|
|
160
|
-
}
|
|
161
|
-
appender.flushSync();
|
|
162
|
-
}
|
|
163
|
-
finally {
|
|
164
|
-
appender.closeSync();
|
|
165
|
-
}
|
|
148
|
+
await (0, appendColumnBatches_js_1.default)(simpleTable.connection, staged, storageTypes.map((type) => (0, parseDuckDBType_js_1.default)(type)), rowCount, (column, start, end) => columnsData[column].slice(start, end));
|
|
166
149
|
if (!needsConversion)
|
|
167
150
|
return;
|
|
168
151
|
// CTAS is atomic: conversion must finish before the destination is replaced.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../../src/methods/log.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AAMvD,wBAA8B,GAAG,CAC/B,WAAW,EAAE,WAAW,EACxB,OAAO,GAAE,KAAK,GAAG,MAAM,GAAG;IACxB,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACvB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CAChB,
|
|
1
|
+
{"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../../src/methods/log.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AAMvD,wBAA8B,GAAG,CAC/B,WAAW,EAAE,WAAW,EACxB,OAAO,GAAE,KAAK,GAAG,MAAM,GAAG;IACxB,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACvB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CAChB,iBAyEP"}
|
package/script/methods/log.js
CHANGED
|
@@ -60,10 +60,7 @@ async function log(simpleTable, options = {}) {
|
|
|
60
60
|
const showing = displayedRows < rowCount
|
|
61
61
|
? ` / showing ${(0, formatNumber_js_1.default)(displayedRows)} rows`
|
|
62
62
|
: "";
|
|
63
|
-
|
|
64
|
-
? ` (charsToLog: ${simpleTable.charsToLog})`
|
|
65
|
-
: "";
|
|
66
|
-
console.log(`${(0, formatNumber_js_1.default)(rowCount)} rows in total${showing}${charsToLog}`);
|
|
63
|
+
console.log(`${(0, formatNumber_js_1.default)(rowCount)} rows in total${showing}`);
|
|
67
64
|
}
|
|
68
65
|
return;
|
|
69
66
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"umap.d.ts","sourceRoot":"","sources":["../../src/methods/umap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AAavD,KAAK,OAAO,GAAG,WAAW,CAAC,UAAU,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE/D,MAAM,CAAC,OAAO,UAAU,IAAI,CAC1B,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,OAAY,QAStB"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = umap;
|
|
7
|
+
const queueOp_js_1 = __importDefault(require("../helpers/queueOp.js"));
|
|
8
|
+
const quoteIdentifier_js_1 = __importDefault(require("../helpers/quoteIdentifier.js"));
|
|
9
|
+
const queryDB_js_1 = __importDefault(require("../helpers/queryDB.js"));
|
|
10
|
+
const mergeOptions_js_1 = __importDefault(require("../helpers/mergeOptions.js"));
|
|
11
|
+
const buildUmapGraph_js_1 = __importDefault(require("../helpers/buildUmapGraph.js"));
|
|
12
|
+
const prepareUmapLayoutOptions_js_1 = __importDefault(require("../helpers/prepareUmapLayoutOptions.js"));
|
|
13
|
+
const readScalarNumber_js_1 = __importDefault(require("../helpers/readScalarNumber.js"));
|
|
14
|
+
const optimizeUmapLayout_js_1 = __importDefault(require("../helpers/optimizeUmapLayout.js"));
|
|
15
|
+
const initialUmapCoordinates_js_1 = __importDefault(require("../helpers/initialUmapCoordinates.js"));
|
|
16
|
+
const appendColumnBatches_js_1 = __importDefault(require("../helpers/appendColumnBatches.js"));
|
|
17
|
+
const node_api_1 = require("@duckdb/node-api");
|
|
18
|
+
function umap(table, column, options = {}) {
|
|
19
|
+
const settings = structuredClone(options);
|
|
20
|
+
(0, queueOp_js_1.default)(table, {
|
|
21
|
+
kind: "barrier",
|
|
22
|
+
method: "umap()",
|
|
23
|
+
parameters: { column, options: settings },
|
|
24
|
+
execute: () => execute(table, column, settings),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
async function execute(table, column, options) {
|
|
28
|
+
const { neighbors = 15, metric = "euclidean" } = options;
|
|
29
|
+
if (!Number.isSafeInteger(neighbors) || neighbors < 2) {
|
|
30
|
+
throw new Error("neighbors must be a safe integer of at least 2.");
|
|
31
|
+
}
|
|
32
|
+
const layoutOptions = (0, prepareUmapLayoutOptions_js_1.default)(options);
|
|
33
|
+
if (!["euclidean", "cosine"].includes(metric)) {
|
|
34
|
+
throw new Error("metric must be euclidean or cosine.");
|
|
35
|
+
}
|
|
36
|
+
const types = await table.getTypes();
|
|
37
|
+
const columns = Object.keys(types);
|
|
38
|
+
const resolve = (name) => {
|
|
39
|
+
const found = columns.find((c) => c.toLowerCase() === name.toLowerCase());
|
|
40
|
+
if (found === undefined)
|
|
41
|
+
throw new Error(`Column ${name} does not exist.`);
|
|
42
|
+
return found;
|
|
43
|
+
};
|
|
44
|
+
const vector = resolve(column);
|
|
45
|
+
if (columns.some((c) => ["umapx", "umapy"].includes(c.toLowerCase()))) {
|
|
46
|
+
throw new Error("UMAP output columns umapX and umapY must not already exist.");
|
|
47
|
+
}
|
|
48
|
+
if (!/^(FLOAT|DOUBLE|TINYINT|SMALLINT|INTEGER|BIGINT|HUGEINT|UTINYINT|USMALLINT|UINTEGER|UBIGINT|UHUGEINT|DECIMAL\(\d+,\d+\))\[\d*\]$/
|
|
49
|
+
.test(types[vector])) {
|
|
50
|
+
throw new Error("UMAP requires a one-dimensional numeric LIST or ARRAY column.");
|
|
51
|
+
}
|
|
52
|
+
const connection = table.connection;
|
|
53
|
+
const [sourceOid, sourceTemporary] = (await connection.runAndReadAll(`SELECT table_oid,temporary FROM duckdb_tables()
|
|
54
|
+
WHERE lower(table_name)=lower($1) AND schema_name=current_schema()
|
|
55
|
+
AND database_name IN (current_database(),'temp')
|
|
56
|
+
ORDER BY temporary DESC LIMIT 1`, [table.name])).getRowsJS()[0] ?? [];
|
|
57
|
+
if (typeof sourceOid !== "number" && typeof sourceOid !== "bigint") {
|
|
58
|
+
throw new Error("UMAP requires a materialized source table.");
|
|
59
|
+
}
|
|
60
|
+
const q = quoteIdentifier_js_1.default;
|
|
61
|
+
const suffix = crypto.randomUUID().replaceAll("-", "");
|
|
62
|
+
const name = (part) => `__sda_umap_${suffix}_${part}`;
|
|
63
|
+
const ordinal = q(name("ordinal"));
|
|
64
|
+
const source = q(table.name), snapshot = q(name("snapshot"));
|
|
65
|
+
const layoutName = name("layout"), layout = q(layoutName);
|
|
66
|
+
const names = {
|
|
67
|
+
rows: q(name("rows")),
|
|
68
|
+
knn: q(name("knn")),
|
|
69
|
+
search: q(name("search")),
|
|
70
|
+
hnsw: q(name("hnsw")),
|
|
71
|
+
candidates: q(name("candidates")),
|
|
72
|
+
scales: q(name("scales")),
|
|
73
|
+
directed: q(name("directed")),
|
|
74
|
+
graph: q(name("graph")),
|
|
75
|
+
};
|
|
76
|
+
const scratch = [
|
|
77
|
+
snapshot,
|
|
78
|
+
layout,
|
|
79
|
+
...Object.entries(names)
|
|
80
|
+
.filter(([key]) => key !== "hnsw").map(([, value]) => value),
|
|
81
|
+
];
|
|
82
|
+
try {
|
|
83
|
+
// Retain payloads in native DuckDB types. A private ordinal distinguishes
|
|
84
|
+
// duplicate rows and is independent of any user column named rowid.
|
|
85
|
+
await connection.run(`CREATE TEMP TABLE ${snapshot} AS
|
|
86
|
+
SELECT *, row_number() OVER () - 1 AS ${ordinal} FROM ${source}`);
|
|
87
|
+
const count = await (0, readScalarNumber_js_1.default)(connection, `SELECT count(*) FROM ${snapshot}`);
|
|
88
|
+
if (count < 3 || count > 0x7fffffff) {
|
|
89
|
+
throw new Error("UMAP requires between 3 and 2147483647 rows.");
|
|
90
|
+
}
|
|
91
|
+
if (await (0, readScalarNumber_js_1.default)(connection, `SELECT count(*) FROM ${snapshot}
|
|
92
|
+
WHERE ${q(vector)} IS NULL OR len(${q(vector)})=0
|
|
93
|
+
OR list_count(${q(vector)}) != len(${q(vector)})
|
|
94
|
+
OR NOT list_bool_and(list_transform(${q(vector)}, x -> isfinite(x::DOUBLE)))`)) {
|
|
95
|
+
throw new Error("Vectors must be nonempty, non-null and contain finite numbers only.");
|
|
96
|
+
}
|
|
97
|
+
if (await (0, readScalarNumber_js_1.default)(connection, `SELECT count(DISTINCT len(${q(vector)})) FROM ${snapshot}`) !== 1) {
|
|
98
|
+
throw new Error("Vectors must have equal dimensions.");
|
|
99
|
+
}
|
|
100
|
+
const dimensions = await (0, readScalarNumber_js_1.default)(connection, `SELECT len(${q(vector)}) FROM ${snapshot} LIMIT 1`);
|
|
101
|
+
await connection.run(`CREATE TEMP TABLE ${names.rows} AS
|
|
102
|
+
SELECT (row_number() OVER (ORDER BY ${ordinal})-1)::INTEGER AS vertex,
|
|
103
|
+
${ordinal} AS ordinal, ${q(vector)}::DOUBLE[${dimensions}] AS vec FROM ${snapshot}`);
|
|
104
|
+
if (await (0, readScalarNumber_js_1.default)(connection, `SELECT count(*) FROM ${names.rows}
|
|
105
|
+
WHERE NOT isfinite(array_inner_product(vec,vec)) OR array_inner_product(vec,vec)>1e300
|
|
106
|
+
${metric === "cosine" ? "OR array_inner_product(vec,vec)=0" : ""}`)) {
|
|
107
|
+
throw new Error("Vector norms overflow, or cosine distance received a zero vector.");
|
|
108
|
+
}
|
|
109
|
+
const graph = await (0, buildUmapGraph_js_1.default)(connection, {
|
|
110
|
+
count,
|
|
111
|
+
dimensions,
|
|
112
|
+
neighbors: Math.min(neighbors, count - 1),
|
|
113
|
+
}, {
|
|
114
|
+
metric,
|
|
115
|
+
search: count <= 1000 ? "exact" : "hnsw",
|
|
116
|
+
}, names);
|
|
117
|
+
// Release index and vector scratch before allocating optimizer state.
|
|
118
|
+
for (const relation of [
|
|
119
|
+
names.search,
|
|
120
|
+
names.candidates,
|
|
121
|
+
names.knn,
|
|
122
|
+
names.directed,
|
|
123
|
+
names.scales,
|
|
124
|
+
names.graph,
|
|
125
|
+
]) {
|
|
126
|
+
await connection.run(`DROP TABLE IF EXISTS ${relation}`);
|
|
127
|
+
}
|
|
128
|
+
await connection.run(`ALTER TABLE ${names.rows} DROP COLUMN vec`);
|
|
129
|
+
const coordinates = (0, optimizeUmapLayout_js_1.default)(graph, (0, initialUmapCoordinates_js_1.default)(count, layoutOptions.seed), layoutOptions);
|
|
130
|
+
await connection.run(`CREATE TEMP TABLE ${layout} (vertex INTEGER,x DOUBLE,y DOUBLE)`);
|
|
131
|
+
await (0, appendColumnBatches_js_1.default)(connection, layoutName, [node_api_1.INTEGER, node_api_1.DOUBLE, node_api_1.DOUBLE], count, (column, start, end) => Array.from({ length: end - start }, (_, row) => column === 0
|
|
132
|
+
? start + row
|
|
133
|
+
: coordinates[2 * (start + row) + column - 1]));
|
|
134
|
+
// Publish in one statement: failed fits never add partial columns. Existing
|
|
135
|
+
// native indexes must be restored because CREATE OR REPLACE drops them.
|
|
136
|
+
const indexes = (await connection.runAndReadAll("SELECT sql FROM duckdb_indexes() WHERE table_oid=$1", [sourceOid])).getRowsJS().map((row) => String(row[0]));
|
|
137
|
+
await connection.run("BEGIN TRANSACTION");
|
|
138
|
+
try {
|
|
139
|
+
await (0, queryDB_js_1.default)(table, `CREATE OR REPLACE ${sourceTemporary ? "TEMP " : ""}TABLE ${source} AS
|
|
140
|
+
SELECT ${columns.map((c) => `s.${q(c)}`).join(",")}, l.x AS "umapX", l.y AS "umapY"
|
|
141
|
+
FROM ${snapshot} s JOIN ${names.rows} r ON s.${ordinal}=r.ordinal
|
|
142
|
+
JOIN ${layout} l ON r.vertex=l.vertex ORDER BY s.${ordinal}`, (0, mergeOptions_js_1.default)(table, {
|
|
143
|
+
table: table.name,
|
|
144
|
+
method: "umap()",
|
|
145
|
+
parameters: { column, options },
|
|
146
|
+
noClean: true,
|
|
147
|
+
}));
|
|
148
|
+
for (const sql of indexes)
|
|
149
|
+
await connection.run(sql);
|
|
150
|
+
await connection.run("COMMIT");
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
await connection.run("ROLLBACK");
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
for (const relation of scratch) {
|
|
159
|
+
await connection.run(`DROP TABLE IF EXISTS ${relation}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -10,7 +10,7 @@ const quoteIdentifier_js_1 = __importDefault(require("../helpers/quoteIdentifier
|
|
|
10
10
|
const tableRegistry_js_1 = require("../helpers/tableRegistry.js");
|
|
11
11
|
const queueOp_js_1 = __importDefault(require("../helpers/queueOp.js"));
|
|
12
12
|
const loadArray_js_1 = require("./loadArray.js");
|
|
13
|
-
const
|
|
13
|
+
const appendColumnBatches_js_1 = __importDefault(require("../helpers/appendColumnBatches.js"));
|
|
14
14
|
const readMutationRows_js_1 = __importDefault(require("../helpers/readMutationRows.js"));
|
|
15
15
|
const parseDuckDBType_js_1 = __importDefault(require("../helpers/parseDuckDBType.js"));
|
|
16
16
|
function updateWithJS(simpleTable, dataModifier, options = {}) {
|
|
@@ -139,26 +139,14 @@ async function executeUpdateWithJS(simpleTable, dataModifier, options) {
|
|
|
139
139
|
(0, parseDuckDBType_js_1.default)(added.types[newKeys.indexOf(key)]));
|
|
140
140
|
await simpleTable.sdb.customQuery(`CREATE OR REPLACE TABLE ${(0, quoteIdentifier_js_1.default)(scratch.name)} (${keys.map((key, i) => `${(0, quoteIdentifier_js_1.default)(key)} ${columnTypes[i]}`)
|
|
141
141
|
.join(", ")})`);
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
? geometryData.get(key).slice(start, end)
|
|
151
|
-
: source.types.has(key)
|
|
152
|
-
? modified.slice(start, end).map((row) => source.toNative(row[key], columnTypes[i]))
|
|
153
|
-
: added.columnsData[newKeys.indexOf(key)].slice(start, end));
|
|
154
|
-
}
|
|
155
|
-
appender.appendDataChunk(chunk);
|
|
156
|
-
}
|
|
157
|
-
appender.flushSync();
|
|
158
|
-
}
|
|
159
|
-
finally {
|
|
160
|
-
appender.closeSync();
|
|
161
|
-
}
|
|
142
|
+
await (0, appendColumnBatches_js_1.default)(simpleTable.connection, scratch.name, columnTypes, modified.length, (column, start, end) => {
|
|
143
|
+
const key = keys[column];
|
|
144
|
+
return outputGeometry.includes(key)
|
|
145
|
+
? geometryData.get(key).slice(start, end)
|
|
146
|
+
: source.types.has(key)
|
|
147
|
+
? modified.slice(start, end).map((row) => source.toNative(row[key], columnTypes[column]))
|
|
148
|
+
: added.columnsData[newKeys.indexOf(key)].slice(start, end);
|
|
149
|
+
});
|
|
162
150
|
const projection = keys.map((key) => outputGeometry.includes(key)
|
|
163
151
|
? `${(0, geometryFromJSON_js_1.default)((0, quoteIdentifier_js_1.default)(key))} AS ${(0, quoteIdentifier_js_1.default)(key)}`
|
|
164
152
|
: outputJSON.includes(key)
|