@graphty/graph-format 0.0.0 → 0.2.0
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/LICENSE +21 -0
- package/README.md +216 -43
- package/dist/graph-format.d.ts +1 -0
- package/dist/graph-format.d.ts.map +1 -0
- package/dist/graph-format.js +15798 -0
- package/dist/graph-format.js.map +1 -0
- package/dist/src/builder/arena.d.ts +85 -0
- package/dist/src/builder/arena.d.ts.map +1 -0
- package/dist/src/builder/arena.js +154 -0
- package/dist/src/builder/arena.js.map +1 -0
- package/dist/src/builder/compact.d.ts +515 -0
- package/dist/src/builder/compact.d.ts.map +1 -0
- package/dist/src/builder/compact.js +1553 -0
- package/dist/src/builder/compact.js.map +1 -0
- package/dist/src/builder/counting-sort.d.ts +96 -0
- package/dist/src/builder/counting-sort.d.ts.map +1 -0
- package/dist/src/builder/counting-sort.js +430 -0
- package/dist/src/builder/counting-sort.js.map +1 -0
- package/dist/src/builder/freeze.d.ts +72 -0
- package/dist/src/builder/freeze.d.ts.map +1 -0
- package/dist/src/builder/freeze.js +548 -0
- package/dist/src/builder/freeze.js.map +1 -0
- package/dist/src/builder/graph-builder.d.ts +602 -0
- package/dist/src/builder/graph-builder.d.ts.map +1 -0
- package/dist/src/builder/graph-builder.js +1940 -0
- package/dist/src/builder/graph-builder.js.map +1 -0
- package/dist/src/builder/options.d.ts +16 -0
- package/dist/src/builder/options.d.ts.map +1 -0
- package/dist/src/builder/options.js +21 -0
- package/dist/src/builder/options.js.map +1 -0
- package/dist/src/columns/bitmap.d.ts +96 -0
- package/dist/src/columns/bitmap.d.ts.map +1 -0
- package/dist/src/columns/bitmap.js +184 -0
- package/dist/src/columns/bitmap.js.map +1 -0
- package/dist/src/columns/column.d.ts +220 -0
- package/dist/src/columns/column.d.ts.map +1 -0
- package/dist/src/columns/column.js +2109 -0
- package/dist/src/columns/column.js.map +1 -0
- package/dist/src/columns/dictionary.d.ts +60 -0
- package/dist/src/columns/dictionary.d.ts.map +1 -0
- package/dist/src/columns/dictionary.js +102 -0
- package/dist/src/columns/dictionary.js.map +1 -0
- package/dist/src/columns/growable.d.ts +198 -0
- package/dist/src/columns/growable.d.ts.map +1 -0
- package/dist/src/columns/growable.js +296 -0
- package/dist/src/columns/growable.js.map +1 -0
- package/dist/src/columns/infer.d.ts +121 -0
- package/dist/src/columns/infer.d.ts.map +1 -0
- package/dist/src/columns/infer.js +268 -0
- package/dist/src/columns/infer.js.map +1 -0
- package/dist/src/columns/remap.d.ts +143 -0
- package/dist/src/columns/remap.d.ts.map +1 -0
- package/dist/src/columns/remap.js +588 -0
- package/dist/src/columns/remap.js.map +1 -0
- package/dist/src/columns/table.d.ts +184 -0
- package/dist/src/columns/table.d.ts.map +1 -0
- package/dist/src/columns/table.js +485 -0
- package/dist/src/columns/table.js.map +1 -0
- package/dist/src/constants.d.ts +34 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.js +79 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/errors.d.ts +76 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/errors.js +32 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/ids/edge-id-index.d.ts +77 -0
- package/dist/src/ids/edge-id-index.d.ts.map +1 -0
- package/dist/src/ids/edge-id-index.js +155 -0
- package/dist/src/ids/edge-id-index.js.map +1 -0
- package/dist/src/ids/node-id-map.d.ts +280 -0
- package/dist/src/ids/node-id-map.d.ts.map +1 -0
- package/dist/src/ids/node-id-map.js +1139 -0
- package/dist/src/ids/node-id-map.js.map +1 -0
- package/dist/src/ids/string-store.d.ts +219 -0
- package/dist/src/ids/string-store.d.ts.map +1 -0
- package/dist/src/ids/string-store.js +526 -0
- package/dist/src/ids/string-store.js.map +1 -0
- package/dist/src/index.d.ts +30 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +29 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/populate/from-csr.d.ts +61 -0
- package/dist/src/populate/from-csr.d.ts.map +1 -0
- package/dist/src/populate/from-csr.js +706 -0
- package/dist/src/populate/from-csr.js.map +1 -0
- package/dist/src/populate/from-edge-arrays.d.ts +51 -0
- package/dist/src/populate/from-edge-arrays.d.ts.map +1 -0
- package/dist/src/populate/from-edge-arrays.js +217 -0
- package/dist/src/populate/from-edge-arrays.js.map +1 -0
- package/dist/src/populate/from-records.d.ts +51 -0
- package/dist/src/populate/from-records.d.ts.map +1 -0
- package/dist/src/populate/from-records.js +447 -0
- package/dist/src/populate/from-records.js.map +1 -0
- package/dist/src/snapshot/derived.d.ts +211 -0
- package/dist/src/snapshot/derived.d.ts.map +1 -0
- package/dist/src/snapshot/derived.js +1254 -0
- package/dist/src/snapshot/derived.js.map +1 -0
- package/dist/src/snapshot/graph-meta.d.ts +23 -0
- package/dist/src/snapshot/graph-meta.d.ts.map +1 -0
- package/dist/src/snapshot/graph-meta.js +159 -0
- package/dist/src/snapshot/graph-meta.js.map +1 -0
- package/dist/src/snapshot/graph-snapshot.d.ts +509 -0
- package/dist/src/snapshot/graph-snapshot.d.ts.map +1 -0
- package/dist/src/snapshot/graph-snapshot.js +1247 -0
- package/dist/src/snapshot/graph-snapshot.js.map +1 -0
- package/dist/src/snapshot/hash.d.ts +90 -0
- package/dist/src/snapshot/hash.d.ts.map +1 -0
- package/dist/src/snapshot/hash.js +228 -0
- package/dist/src/snapshot/hash.js.map +1 -0
- package/dist/src/snapshot/queries.d.ts +74 -0
- package/dist/src/snapshot/queries.d.ts.map +1 -0
- package/dist/src/snapshot/queries.js +131 -0
- package/dist/src/snapshot/queries.js.map +1 -0
- package/dist/src/snapshot/validate.d.ts +192 -0
- package/dist/src/snapshot/validate.d.ts.map +1 -0
- package/dist/src/snapshot/validate.js +990 -0
- package/dist/src/snapshot/validate.js.map +1 -0
- package/dist/src/snapshot/views.d.ts +274 -0
- package/dist/src/snapshot/views.d.ts.map +1 -0
- package/dist/src/snapshot/views.js +856 -0
- package/dist/src/snapshot/views.js.map +1 -0
- package/dist/src/types/builder.d.ts +456 -0
- package/dist/src/types/builder.d.ts.map +1 -0
- package/dist/src/types/builder.js +11 -0
- package/dist/src/types/builder.js.map +1 -0
- package/dist/src/types/columns.d.ts +615 -0
- package/dist/src/types/columns.d.ts.map +1 -0
- package/dist/src/types/columns.js +14 -0
- package/dist/src/types/columns.js.map +1 -0
- package/dist/src/types/index.d.ts +14 -0
- package/dist/src/types/index.d.ts.map +1 -0
- package/dist/src/types/index.js +11 -0
- package/dist/src/types/index.js.map +1 -0
- package/dist/src/types/internal.d.ts +172 -0
- package/dist/src/types/internal.d.ts.map +1 -0
- package/dist/src/types/internal.js +13 -0
- package/dist/src/types/internal.js.map +1 -0
- package/dist/src/types/snapshot.d.ts +801 -0
- package/dist/src/types/snapshot.d.ts.map +1 -0
- package/dist/src/types/snapshot.js +14 -0
- package/dist/src/types/snapshot.js.map +1 -0
- package/dist/src/types/wire.d.ts +196 -0
- package/dist/src/types/wire.d.ts.map +1 -0
- package/dist/src/types/wire.js +8 -0
- package/dist/src/types/wire.js.map +1 -0
- package/dist/src/util/mask.d.ts +55 -0
- package/dist/src/util/mask.d.ts.map +1 -0
- package/dist/src/util/mask.js +71 -0
- package/dist/src/util/mask.js.map +1 -0
- package/dist/src/util/options.d.ts +22 -0
- package/dist/src/util/options.d.ts.map +1 -0
- package/dist/src/util/options.js +49 -0
- package/dist/src/util/options.js.map +1 -0
- package/dist/src/util/shared-buffers.d.ts +42 -0
- package/dist/src/util/shared-buffers.d.ts.map +1 -0
- package/dist/src/util/shared-buffers.js +52 -0
- package/dist/src/util/shared-buffers.js.map +1 -0
- package/dist/src/util/typed-array.d.ts +205 -0
- package/dist/src/util/typed-array.d.ts.map +1 -0
- package/dist/src/util/typed-array.js +289 -0
- package/dist/src/util/typed-array.js.map +1 -0
- package/dist/src/wire/bytes.d.ts +69 -0
- package/dist/src/wire/bytes.d.ts.map +1 -0
- package/dist/src/wire/bytes.js +361 -0
- package/dist/src/wire/bytes.js.map +1 -0
- package/dist/src/wire/carried-views.d.ts +111 -0
- package/dist/src/wire/carried-views.d.ts.map +1 -0
- package/dist/src/wire/carried-views.js +286 -0
- package/dist/src/wire/carried-views.js.map +1 -0
- package/dist/src/wire/from-wire.d.ts +144 -0
- package/dist/src/wire/from-wire.d.ts.map +1 -0
- package/dist/src/wire/from-wire.js +1425 -0
- package/dist/src/wire/from-wire.js.map +1 -0
- package/dist/src/wire/to-wire.d.ts +171 -0
- package/dist/src/wire/to-wire.d.ts.map +1 -0
- package/dist/src/wire/to-wire.js +652 -0
- package/dist/src/wire/to-wire.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/package.json +72 -7
- package/src/builder/arena.ts +192 -0
- package/src/builder/compact.ts +1811 -0
- package/src/builder/counting-sort.ts +506 -0
- package/src/builder/freeze.ts +689 -0
- package/src/builder/graph-builder.ts +2264 -0
- package/src/builder/options.ts +29 -0
- package/src/columns/bitmap.ts +196 -0
- package/src/columns/column.ts +2574 -0
- package/src/columns/dictionary.ts +114 -0
- package/src/columns/growable.ts +350 -0
- package/src/columns/infer.ts +285 -0
- package/src/columns/remap.ts +663 -0
- package/src/columns/table.ts +568 -0
- package/src/constants.ts +89 -0
- package/src/errors.ts +115 -0
- package/src/ids/edge-id-index.ts +179 -0
- package/src/ids/node-id-map.ts +1340 -0
- package/src/ids/string-store.ts +589 -0
- package/src/index.ts +139 -0
- package/src/lib-resizable-array-buffer.d.ts +34 -0
- package/src/populate/from-csr.ts +802 -0
- package/src/populate/from-edge-arrays.ts +257 -0
- package/src/populate/from-records.ts +536 -0
- package/src/snapshot/derived.ts +1486 -0
- package/src/snapshot/graph-meta.ts +167 -0
- package/src/snapshot/graph-snapshot.ts +1503 -0
- package/src/snapshot/hash.ts +241 -0
- package/src/snapshot/queries.ts +136 -0
- package/src/snapshot/validate.ts +1108 -0
- package/src/snapshot/views.ts +943 -0
- package/src/types/builder.ts +480 -0
- package/src/types/columns.ts +727 -0
- package/src/types/index.ts +14 -0
- package/src/types/internal.ts +195 -0
- package/src/types/snapshot.ts +886 -0
- package/src/types/wire.ts +207 -0
- package/src/util/mask.ts +89 -0
- package/src/util/options.ts +57 -0
- package/src/util/shared-buffers.ts +56 -0
- package/src/util/typed-array.ts +371 -0
- package/src/wire/bytes.ts +440 -0
- package/src/wire/carried-views.ts +327 -0
- package/src/wire/from-wire.ts +1690 -0
- package/src/wire/to-wire.ts +755 -0
|
@@ -0,0 +1,990 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `validate()` (design sections 3.2, 9.5 and 11.4): the invariants I1-I13 as one function each, the
|
|
3
|
+
* two levels "structure" (O(n + m + columns): lengths, ranges, counts, alignment, column length
|
|
4
|
+
* rules) and "full" (O(m log d): sortedness, orientation, pairing, NaN, flag recomputation, id
|
|
5
|
+
* bijection, unique columns), and the flag predicates of design section 3.8 shared with the derived
|
|
6
|
+
* graph core builder so that every flag has exactly one definition.
|
|
7
|
+
*
|
|
8
|
+
* Every check throws `E_INVALID_SNAPSHOT` on the first violation with `details.invariant` naming the
|
|
9
|
+
* number ("I4") and the location (`row`, `arc`, `edge`, `column`, `table`, `flag`, `reason`). The
|
|
10
|
+
* functions take the public `GraphSnapshot` contract, so test/invariants.test.ts runs the same
|
|
11
|
+
* helpers over hand-written fixtures. Checksum comparison needs the snapshot's private records and
|
|
12
|
+
* lives in graph-snapshot.ts.
|
|
13
|
+
*/
|
|
14
|
+
import { bitmapCount, bitmapGet } from "../columns/bitmap.js";
|
|
15
|
+
import { verifyUniqueColumn } from "../columns/table.js";
|
|
16
|
+
import { ALIGNMENT, INVALID_INDEX, MAX_COUNT } from "../constants.js";
|
|
17
|
+
import { GraphFormatError } from "../errors.js";
|
|
18
|
+
import { isFourByteAligned, isOverPlainBuffer } from "../util/typed-array.js";
|
|
19
|
+
// ============================================================ errors
|
|
20
|
+
/**
|
|
21
|
+
* Build the E_INVALID_SNAPSHOT error of one violated invariant.
|
|
22
|
+
* @param invariant - the invariant number, e.g. "I4"
|
|
23
|
+
* @param message - a plain-ASCII description naming the location
|
|
24
|
+
* @param details - the location (row, arc, edge, column, ...); `invariant` is added
|
|
25
|
+
* @returns the error
|
|
26
|
+
*/
|
|
27
|
+
export function invariantViolation(invariant, message, details = {}) {
|
|
28
|
+
return new GraphFormatError("E_INVALID_SNAPSHOT", `invariant ${invariant} violated: ${message}`, {
|
|
29
|
+
invariant,
|
|
30
|
+
...details,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Whether some row holds two arcs with equal colIdx (parallel edges); adjacent by invariant I4.
|
|
35
|
+
* @param rowPtr - the row offsets
|
|
36
|
+
* @param colIdx - the sorted targets
|
|
37
|
+
* @param nodeCount - the node count
|
|
38
|
+
* @returns true for a multigraph
|
|
39
|
+
*/
|
|
40
|
+
function hasParallelArcs(rowPtr, colIdx, nodeCount) {
|
|
41
|
+
for (let u = 0; u < nodeCount; u++) {
|
|
42
|
+
const end = rowPtr[u + 1];
|
|
43
|
+
for (let a = rowPtr[u] + 1; a < end; a++) {
|
|
44
|
+
if (colIdx[a] === colIdx[a - 1]) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The number of arcs a with colIdx[a] === row(a).
|
|
53
|
+
* @param rowPtr - the row offsets
|
|
54
|
+
* @param colIdx - the sorted targets
|
|
55
|
+
* @param nodeCount - the node count
|
|
56
|
+
* @returns the loop arc count
|
|
57
|
+
*/
|
|
58
|
+
export function countLoopArcs(rowPtr, colIdx, nodeCount) {
|
|
59
|
+
let loops = 0;
|
|
60
|
+
for (let u = 0; u < nodeCount; u++) {
|
|
61
|
+
const end = rowPtr[u + 1];
|
|
62
|
+
for (let a = rowPtr[u]; a < end; a++) {
|
|
63
|
+
if (colIdx[a] === u) {
|
|
64
|
+
loops++;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return loops;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Whether a permutation array is the identity.
|
|
72
|
+
* @param perm - the array
|
|
73
|
+
* @returns true when perm[i] === i for every i
|
|
74
|
+
*/
|
|
75
|
+
export function isIdentity(perm) {
|
|
76
|
+
for (let i = 0; i < perm.length; i++) {
|
|
77
|
+
if (perm[i] !== i) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Compute every flag of design section 3.8 from the arrays (invariant I9: never guessed).
|
|
85
|
+
* @param core - the arrays and counts the predicates read
|
|
86
|
+
* @returns the truthful flags
|
|
87
|
+
*/
|
|
88
|
+
export function computeFlags(core) {
|
|
89
|
+
const { directed, nodeCount, rowPtr, colIdx, weights, arcToEdge } = core;
|
|
90
|
+
let allWeightsOne = true;
|
|
91
|
+
let nonNegativeWeights = true;
|
|
92
|
+
let finiteWeights = true;
|
|
93
|
+
if (weights !== null) {
|
|
94
|
+
for (let a = 0; a < weights.length; a++) {
|
|
95
|
+
const w = weights[a];
|
|
96
|
+
if (w !== 1) {
|
|
97
|
+
allWeightsOne = false;
|
|
98
|
+
}
|
|
99
|
+
if (!(w >= 0)) {
|
|
100
|
+
nonNegativeWeights = false;
|
|
101
|
+
}
|
|
102
|
+
if (!Number.isFinite(w)) {
|
|
103
|
+
finiteWeights = false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
multigraph: hasParallelArcs(rowPtr, colIdx, nodeCount),
|
|
109
|
+
hasSelfLoops: core.selfLoopCount > 0,
|
|
110
|
+
arcToEdgeIsIdentity: directed && (arcToEdge === null || isIdentity(arcToEdge)),
|
|
111
|
+
weighted: weights !== null,
|
|
112
|
+
allWeightsOne,
|
|
113
|
+
nonNegativeWeights,
|
|
114
|
+
finiteWeights,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
// ============================================================ I1 - I3: structure
|
|
118
|
+
/**
|
|
119
|
+
* I1: rowPtr has nodeCount + 1 entries, starts at 0, is non-decreasing and ends at arcCount ===
|
|
120
|
+
* colIdx.length.
|
|
121
|
+
* @param s - the snapshot
|
|
122
|
+
*/
|
|
123
|
+
export function checkI1(s) {
|
|
124
|
+
const { rowPtr, colIdx, nodeCount, arcCount } = s;
|
|
125
|
+
if (rowPtr.length !== nodeCount + 1) {
|
|
126
|
+
throw invariantViolation("I1", `rowPtr has ${rowPtr.length} entries, expected ${nodeCount + 1}`, {
|
|
127
|
+
expected: nodeCount + 1,
|
|
128
|
+
found: rowPtr.length,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
if (rowPtr[0] !== 0) {
|
|
132
|
+
throw invariantViolation("I1", `rowPtr[0] is ${rowPtr[0]}, expected 0`, { row: 0, found: rowPtr[0] });
|
|
133
|
+
}
|
|
134
|
+
for (let u = 0; u < nodeCount; u++) {
|
|
135
|
+
if (rowPtr[u + 1] < rowPtr[u]) {
|
|
136
|
+
throw invariantViolation("I1", `rowPtr decreases at row ${u}: ${rowPtr[u]} -> ${rowPtr[u + 1]}`, {
|
|
137
|
+
row: u,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (rowPtr[nodeCount] !== arcCount || colIdx.length !== arcCount) {
|
|
142
|
+
throw invariantViolation("I1", `rowPtr[${nodeCount}] = ${rowPtr[nodeCount]}, colIdx.length = ${colIdx.length}, arcCount = ${arcCount}`, { row: nodeCount, found: rowPtr[nodeCount], expected: arcCount, colIdxLength: colIdx.length });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* I2: every colIdx entry is below nodeCount (so INVALID_INDEX never appears).
|
|
147
|
+
* @param s - the snapshot
|
|
148
|
+
*/
|
|
149
|
+
export function checkI2(s) {
|
|
150
|
+
const { colIdx, nodeCount } = s;
|
|
151
|
+
for (let a = 0; a < colIdx.length; a++) {
|
|
152
|
+
if (colIdx[a] >= nodeCount) {
|
|
153
|
+
throw invariantViolation("I2", `colIdx[${a}] = ${colIdx[a]} is not below nodeCount ${nodeCount}`, {
|
|
154
|
+
arc: a,
|
|
155
|
+
found: colIdx[a],
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* I3: the counts are non-negative integers at most MAX_COUNT, edgeCount <= arcCount and selfLoopCount
|
|
162
|
+
* <= edgeCount.
|
|
163
|
+
* @param s - the snapshot
|
|
164
|
+
*/
|
|
165
|
+
export function checkI3(s) {
|
|
166
|
+
const counts = [
|
|
167
|
+
["nodeCount", s.nodeCount],
|
|
168
|
+
["edgeCount", s.edgeCount],
|
|
169
|
+
["arcCount", s.arcCount],
|
|
170
|
+
["selfLoopCount", s.selfLoopCount],
|
|
171
|
+
];
|
|
172
|
+
for (const [name, value] of counts) {
|
|
173
|
+
if (!Number.isInteger(value) || value < 0 || value > MAX_COUNT) {
|
|
174
|
+
throw invariantViolation("I3", `${name} = ${value} is not an integer in [0, MAX_COUNT]`, {
|
|
175
|
+
count: name,
|
|
176
|
+
found: value,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (s.edgeCount > s.arcCount) {
|
|
181
|
+
throw invariantViolation("I3", `edgeCount ${s.edgeCount} exceeds arcCount ${s.arcCount}`, {
|
|
182
|
+
edgeCount: s.edgeCount,
|
|
183
|
+
arcCount: s.arcCount,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
if (s.selfLoopCount > s.edgeCount) {
|
|
187
|
+
throw invariantViolation("I3", `selfLoopCount ${s.selfLoopCount} exceeds edgeCount ${s.edgeCount}`, {
|
|
188
|
+
selfLoopCount: s.selfLoopCount,
|
|
189
|
+
edgeCount: s.edgeCount,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// ============================================================ I4 - I8: rows, permutations, pairing, weights
|
|
194
|
+
/**
|
|
195
|
+
* I4: within every row colIdx is non-decreasing and arcs with equal colIdx are ordered by ascending
|
|
196
|
+
* arcToEdge. Full level.
|
|
197
|
+
* @param s - the snapshot
|
|
198
|
+
*/
|
|
199
|
+
export function checkI4(s) {
|
|
200
|
+
const { rowPtr, colIdx, nodeCount } = s;
|
|
201
|
+
const identity = s.flags.arcToEdgeIsIdentity;
|
|
202
|
+
const arcToEdge = identity ? null : s.arcToEdge;
|
|
203
|
+
for (let u = 0; u < nodeCount; u++) {
|
|
204
|
+
const end = rowPtr[u + 1];
|
|
205
|
+
for (let a = rowPtr[u] + 1; a < end; a++) {
|
|
206
|
+
if (colIdx[a] < colIdx[a - 1]) {
|
|
207
|
+
throw invariantViolation("I4", `row ${u} is not sorted at arc ${a}`, { row: u, arc: a });
|
|
208
|
+
}
|
|
209
|
+
if (colIdx[a] === colIdx[a - 1] && arcToEdge !== null && arcToEdge[a] <= arcToEdge[a - 1]) {
|
|
210
|
+
throw invariantViolation("I4", `parallel arcs ${a - 1} and ${a} in row ${u} are not in edge order`, {
|
|
211
|
+
row: u,
|
|
212
|
+
arc: a,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* I5 (structure part): arcToEdge has arcCount entries below edgeCount and edgeToArc has edgeCount
|
|
220
|
+
* entries below arcCount; an identity flag requires arcCount === edgeCount and a directed snapshot.
|
|
221
|
+
* @param s - the snapshot
|
|
222
|
+
*/
|
|
223
|
+
export function checkI5Ranges(s) {
|
|
224
|
+
const { arcCount, edgeCount } = s;
|
|
225
|
+
if (s.flags.arcToEdgeIsIdentity) {
|
|
226
|
+
if (!s.directed || arcCount !== edgeCount) {
|
|
227
|
+
throw invariantViolation("I5", "identity permutation claimed on a snapshot where it cannot hold", {
|
|
228
|
+
directed: s.directed,
|
|
229
|
+
arcCount,
|
|
230
|
+
edgeCount,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const { arcToEdge, edgeToArc } = s;
|
|
236
|
+
if (arcToEdge.length !== arcCount) {
|
|
237
|
+
throw invariantViolation("I5", `arcToEdge has ${arcToEdge.length} entries, expected ${arcCount}`, {
|
|
238
|
+
expected: arcCount,
|
|
239
|
+
found: arcToEdge.length,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
if (edgeToArc.length !== edgeCount) {
|
|
243
|
+
throw invariantViolation("I5", `edgeToArc has ${edgeToArc.length} entries, expected ${edgeCount}`, {
|
|
244
|
+
expected: edgeCount,
|
|
245
|
+
found: edgeToArc.length,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
for (let a = 0; a < arcCount; a++) {
|
|
249
|
+
if (arcToEdge[a] >= edgeCount) {
|
|
250
|
+
throw invariantViolation("I5", `arcToEdge[${a}] = ${arcToEdge[a]} is not below edgeCount ${edgeCount}`, {
|
|
251
|
+
arc: a,
|
|
252
|
+
found: arcToEdge[a],
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
for (let e = 0; e < edgeCount; e++) {
|
|
257
|
+
if (edgeToArc[e] >= arcCount) {
|
|
258
|
+
throw invariantViolation("I5", `edgeToArc[${e}] = ${edgeToArc[e]} is not below arcCount ${arcCount}`, {
|
|
259
|
+
edge: e,
|
|
260
|
+
found: edgeToArc[e],
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* I5 (orientation part): arcToEdge[edgeToArc[e]] === e for every edge. Full level.
|
|
267
|
+
* @param s - the snapshot
|
|
268
|
+
*/
|
|
269
|
+
export function checkI5Orientation(s) {
|
|
270
|
+
if (s.flags.arcToEdgeIsIdentity) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const { arcToEdge, edgeToArc, edgeCount } = s;
|
|
274
|
+
for (let e = 0; e < edgeCount; e++) {
|
|
275
|
+
if (arcToEdge[edgeToArc[e]] !== e) {
|
|
276
|
+
throw invariantViolation("I5", `arcToEdge[edgeToArc[${e}]] = ${arcToEdge[edgeToArc[e]]}, expected ${e}`, {
|
|
277
|
+
edge: e,
|
|
278
|
+
arc: edgeToArc[e],
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* I6: a directed snapshot has arcCount === edgeCount (structure) and arcToEdge is a permutation of
|
|
285
|
+
* 0..edgeCount-1 (full: every edge exactly once).
|
|
286
|
+
* @param s - the snapshot
|
|
287
|
+
* @param full - whether to run the permutation check
|
|
288
|
+
*/
|
|
289
|
+
export function checkI6(s, full) {
|
|
290
|
+
if (!s.directed) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const { arcCount, edgeCount } = s;
|
|
294
|
+
if (arcCount !== edgeCount) {
|
|
295
|
+
throw invariantViolation("I6", `directed snapshot has arcCount ${arcCount} but edgeCount ${edgeCount}`, {
|
|
296
|
+
arcCount,
|
|
297
|
+
edgeCount,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
if (!full || s.flags.arcToEdgeIsIdentity) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const { arcToEdge } = s;
|
|
304
|
+
const seen = new Uint8Array(edgeCount);
|
|
305
|
+
for (let a = 0; a < arcCount; a++) {
|
|
306
|
+
const e = arcToEdge[a];
|
|
307
|
+
if (e >= edgeCount || seen[e] === 1) {
|
|
308
|
+
throw invariantViolation("I6", `edge ${e} appears more than once in arcToEdge (arc ${a})`, {
|
|
309
|
+
arc: a,
|
|
310
|
+
edge: e,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
seen[e] = 1;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* I7 (structure part): an undirected snapshot has arcCount === 2 * edgeCount - selfLoopCount.
|
|
318
|
+
* @param s - the snapshot
|
|
319
|
+
*/
|
|
320
|
+
export function checkI7Counts(s) {
|
|
321
|
+
if (s.directed) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const expected = 2 * s.edgeCount - s.selfLoopCount;
|
|
325
|
+
if (s.arcCount !== expected) {
|
|
326
|
+
throw invariantViolation("I7", `undirected snapshot has arcCount ${s.arcCount}, expected 2 * ${s.edgeCount} - ${s.selfLoopCount} = ${expected}`, { arcCount: s.arcCount, expected });
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* I7 (pairing part): every arc u -> v with u !== v has exactly one mate v -> u with the same logical
|
|
331
|
+
* edge and weight, a self-loop edge has exactly one arc, and every edge appears once (loop) or twice
|
|
332
|
+
* (mates). The lockstep walk of design section 6.4. Full level.
|
|
333
|
+
* @param s - the snapshot
|
|
334
|
+
*/
|
|
335
|
+
export function checkI7Pairing(s) {
|
|
336
|
+
if (s.directed) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
const { nodeCount, arcCount, edgeCount, rowPtr, colIdx, weights, arcToEdge } = s;
|
|
340
|
+
const occurrences = new Uint8Array(edgeCount);
|
|
341
|
+
for (let a = 0; a < arcCount; a++) {
|
|
342
|
+
const e = arcToEdge[a];
|
|
343
|
+
if (occurrences[e] === 2) {
|
|
344
|
+
throw invariantViolation("I7", `edge ${e} appears more than twice in arcToEdge (arc ${a})`, {
|
|
345
|
+
arc: a,
|
|
346
|
+
edge: e,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
occurrences[e]++;
|
|
350
|
+
}
|
|
351
|
+
const cursor = rowPtr.slice(0, nodeCount);
|
|
352
|
+
for (let u = 0; u < nodeCount; u++) {
|
|
353
|
+
const end = rowPtr[u + 1];
|
|
354
|
+
let a = cursor[u];
|
|
355
|
+
if (a < rowPtr[u] || a > end) {
|
|
356
|
+
throw invariantViolation("I7", `row ${u} was over-consumed by mates from earlier rows`, { row: u });
|
|
357
|
+
}
|
|
358
|
+
while (a < end) {
|
|
359
|
+
const v = colIdx[a];
|
|
360
|
+
let g = a + 1;
|
|
361
|
+
while (g < end && colIdx[g] === v) {
|
|
362
|
+
g++;
|
|
363
|
+
}
|
|
364
|
+
const k = g - a;
|
|
365
|
+
if (v === u) {
|
|
366
|
+
for (let i = a; i < g; i++) {
|
|
367
|
+
if (occurrences[arcToEdge[i]] !== 1) {
|
|
368
|
+
throw invariantViolation("I7", `self-loop edge ${arcToEdge[i]} has more than one arc`, {
|
|
369
|
+
row: u,
|
|
370
|
+
arc: i,
|
|
371
|
+
edge: arcToEdge[i],
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
if (v < u) {
|
|
378
|
+
throw invariantViolation("I7", `arc ${a} in row ${u} targets ${v} but row ${v} holds no mate`, {
|
|
379
|
+
row: u,
|
|
380
|
+
arc: a,
|
|
381
|
+
target: v,
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
const b = cursor[v];
|
|
385
|
+
const rowEnd = rowPtr[v + 1];
|
|
386
|
+
for (let i = 0; i < k; i++) {
|
|
387
|
+
const arc = a + i;
|
|
388
|
+
const mate = b + i;
|
|
389
|
+
if (mate >= rowEnd || colIdx[mate] !== u) {
|
|
390
|
+
throw invariantViolation("I7", `arc ${arc} in row ${u} targeting ${v} has no mate in row ${v}`, {
|
|
391
|
+
row: u,
|
|
392
|
+
arc,
|
|
393
|
+
target: v,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
if (arcToEdge[mate] !== arcToEdge[arc]) {
|
|
397
|
+
throw invariantViolation("I7", `arcs ${arc} and ${mate} should share a logical edge but hold ${arcToEdge[arc]} and ${arcToEdge[mate]}`, { row: u, arc, mate });
|
|
398
|
+
}
|
|
399
|
+
if (occurrences[arcToEdge[arc]] !== 2) {
|
|
400
|
+
throw invariantViolation("I7", `edge ${arcToEdge[arc]} of arc ${arc} does not have two arcs`, {
|
|
401
|
+
row: u,
|
|
402
|
+
arc,
|
|
403
|
+
edge: arcToEdge[arc],
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
if (weights !== null && weights[arc] !== weights[mate]) {
|
|
407
|
+
throw invariantViolation("I7", `arcs ${arc} and ${mate} carry different weights ${weights[arc]} and ${weights[mate]}`, { row: u, arc, mate });
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (b + k < rowEnd && colIdx[b + k] === u) {
|
|
411
|
+
throw invariantViolation("I7", `row ${v} holds more arcs to ${u} than row ${u} holds to ${v}`, {
|
|
412
|
+
row: v,
|
|
413
|
+
arc: b + k,
|
|
414
|
+
target: u,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
cursor[v] = b + k;
|
|
418
|
+
}
|
|
419
|
+
a = g;
|
|
420
|
+
}
|
|
421
|
+
cursor[u] = end;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* I8 (length part): weights is null or has arcCount entries.
|
|
426
|
+
* @param s - the snapshot
|
|
427
|
+
*/
|
|
428
|
+
export function checkI8Length(s) {
|
|
429
|
+
const { weights, arcCount } = s;
|
|
430
|
+
if (weights !== null && weights.length !== arcCount) {
|
|
431
|
+
throw invariantViolation("I8", `weights has ${weights.length} entries, expected ${arcCount}`, {
|
|
432
|
+
expected: arcCount,
|
|
433
|
+
found: weights.length,
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* I8 (value part): no weight is NaN. Full level.
|
|
439
|
+
* @param s - the snapshot
|
|
440
|
+
*/
|
|
441
|
+
export function checkI8NaN(s) {
|
|
442
|
+
const { weights } = s;
|
|
443
|
+
if (weights === null) {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
for (let a = 0; a < weights.length; a++) {
|
|
447
|
+
if (Number.isNaN(weights[a])) {
|
|
448
|
+
throw invariantViolation("I8", `weights[${a}] is NaN`, { arc: a });
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
// ============================================================ I9 - I13: flags, alignment, ids, columns
|
|
453
|
+
/**
|
|
454
|
+
* I9: every flag equals its predicate over the arrays, and selfLoopCount equals the number of loop
|
|
455
|
+
* arcs. Full level.
|
|
456
|
+
* @param s - the snapshot
|
|
457
|
+
*/
|
|
458
|
+
export function checkI9(s) {
|
|
459
|
+
const loopArcs = countLoopArcs(s.rowPtr, s.colIdx, s.nodeCount);
|
|
460
|
+
if (loopArcs !== s.selfLoopCount) {
|
|
461
|
+
throw invariantViolation("I9", `selfLoopCount is ${s.selfLoopCount} but ${loopArcs} loop arcs exist`, {
|
|
462
|
+
reason: "selfLoopCount",
|
|
463
|
+
expected: loopArcs,
|
|
464
|
+
found: s.selfLoopCount,
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
const recomputed = computeFlags({
|
|
468
|
+
directed: s.directed,
|
|
469
|
+
nodeCount: s.nodeCount,
|
|
470
|
+
rowPtr: s.rowPtr,
|
|
471
|
+
colIdx: s.colIdx,
|
|
472
|
+
weights: s.weights,
|
|
473
|
+
arcToEdge: s.flags.arcToEdgeIsIdentity ? null : s.arcToEdge,
|
|
474
|
+
selfLoopCount: s.selfLoopCount,
|
|
475
|
+
});
|
|
476
|
+
const names = [
|
|
477
|
+
"multigraph",
|
|
478
|
+
"hasSelfLoops",
|
|
479
|
+
"arcToEdgeIsIdentity",
|
|
480
|
+
"weighted",
|
|
481
|
+
"allWeightsOne",
|
|
482
|
+
"nonNegativeWeights",
|
|
483
|
+
"finiteWeights",
|
|
484
|
+
];
|
|
485
|
+
for (const name of names) {
|
|
486
|
+
if (s.flags[name] !== recomputed[name]) {
|
|
487
|
+
throw invariantViolation("I9", `flag ${name} is ${s.flags[name]} but the arrays say ${recomputed[name]}`, {
|
|
488
|
+
flag: name,
|
|
489
|
+
found: s.flags[name],
|
|
490
|
+
expected: recomputed[name],
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Check one core array against its arena segment (design section 10.3).
|
|
497
|
+
* @param arena - the arena
|
|
498
|
+
* @param name - the core array name
|
|
499
|
+
* @param array - the array, or null when absent
|
|
500
|
+
*/
|
|
501
|
+
function checkSegment(arena, name, array) {
|
|
502
|
+
const segment = arena.segments[name];
|
|
503
|
+
if (segment === null) {
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
if (array === null) {
|
|
507
|
+
throw invariantViolation("I10", `arena names a segment for absent core array ${name}`, { array: name });
|
|
508
|
+
}
|
|
509
|
+
const relative = segment.byteOffset - arena.byteOffset;
|
|
510
|
+
if (relative < 0 || relative % ALIGNMENT !== 0 || relative + segment.byteLength > arena.byteLength) {
|
|
511
|
+
throw invariantViolation("I10", `arena segment ${name} at ${segment.byteOffset} is not 256-aligned inside the arena`, {
|
|
512
|
+
array: name,
|
|
513
|
+
byteOffset: segment.byteOffset,
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
if (array.buffer !== arena.buffer ||
|
|
517
|
+
array.byteOffset !== segment.byteOffset ||
|
|
518
|
+
array.byteLength !== segment.byteLength) {
|
|
519
|
+
throw invariantViolation("I10", `core array ${name} is not the view its arena segment describes`, {
|
|
520
|
+
array: name,
|
|
521
|
+
byteOffset: array.byteOffset,
|
|
522
|
+
byteLength: array.byteLength,
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* I10: every core array is 4-byte aligned over a plain ArrayBuffer and, when an arena exists, every
|
|
528
|
+
* array with a non-null segment is the 256-aligned view the segment describes and hotByteLength is the
|
|
529
|
+
* end of the weights (or colIdx, or rowPtr) segment.
|
|
530
|
+
* @param s - the snapshot
|
|
531
|
+
*/
|
|
532
|
+
export function checkI10(s) {
|
|
533
|
+
const identity = s.flags.arcToEdgeIsIdentity;
|
|
534
|
+
const arrays = [
|
|
535
|
+
["rowPtr", s.rowPtr],
|
|
536
|
+
["colIdx", s.colIdx],
|
|
537
|
+
["weights", s.weights],
|
|
538
|
+
["arcToEdge", identity ? null : s.arcToEdge],
|
|
539
|
+
["edgeToArc", identity ? null : s.edgeToArc],
|
|
540
|
+
];
|
|
541
|
+
for (const [name, array] of arrays) {
|
|
542
|
+
if (array === null) {
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
if (!isFourByteAligned(array)) {
|
|
546
|
+
throw invariantViolation("I10", `core array ${name} is not 4-byte aligned`, {
|
|
547
|
+
array: name,
|
|
548
|
+
byteOffset: array.byteOffset,
|
|
549
|
+
byteLength: array.byteLength,
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
if (!isOverPlainBuffer(array)) {
|
|
553
|
+
throw invariantViolation("I10", `core array ${name} is not a view over a plain ArrayBuffer`, {
|
|
554
|
+
array: name,
|
|
555
|
+
reason: "buffer",
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
const { arena } = s;
|
|
560
|
+
if (arena === null) {
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
const { alignment } = arena;
|
|
564
|
+
if (alignment !== ALIGNMENT) {
|
|
565
|
+
throw invariantViolation("I10", `arena alignment is ${alignment}, expected ${ALIGNMENT}`, {
|
|
566
|
+
found: alignment,
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
for (const [name, array] of arrays) {
|
|
570
|
+
checkSegment(arena, name, array);
|
|
571
|
+
}
|
|
572
|
+
let hotEnd = 0;
|
|
573
|
+
for (const name of ["rowPtr", "colIdx", "weights"]) {
|
|
574
|
+
const segment = arena.segments[name];
|
|
575
|
+
if (segment !== null) {
|
|
576
|
+
hotEnd = segment.byteOffset + segment.byteLength - arena.byteOffset;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
if (arena.hotByteLength !== hotEnd) {
|
|
580
|
+
throw invariantViolation("I10", `arena.hotByteLength is ${arena.hotByteLength}, expected ${hotEnd}`, {
|
|
581
|
+
reason: "hotByteLength",
|
|
582
|
+
found: arena.hotByteLength,
|
|
583
|
+
expected: hotEnd,
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* I11 (size part): the id map has nodeCount ids.
|
|
589
|
+
* @param s - the snapshot
|
|
590
|
+
*/
|
|
591
|
+
export function checkI11Size(s) {
|
|
592
|
+
if (s.ids.size !== s.nodeCount) {
|
|
593
|
+
throw invariantViolation("I11", `ids.size is ${s.ids.size}, expected nodeCount ${s.nodeCount}`, {
|
|
594
|
+
expected: s.nodeCount,
|
|
595
|
+
found: s.ids.size,
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* I11 (bijection part): ids.indexOf(ids.idOf(i)) === i for every i and no id is NaN. Full level.
|
|
601
|
+
* @param s - the snapshot
|
|
602
|
+
*/
|
|
603
|
+
export function checkI11Bijection(s) {
|
|
604
|
+
const { ids, nodeCount } = s;
|
|
605
|
+
for (let i = 0; i < nodeCount; i++) {
|
|
606
|
+
const id = ids.idOf(i);
|
|
607
|
+
if (typeof id === "number" && Number.isNaN(id)) {
|
|
608
|
+
throw invariantViolation("I11", `id of node ${i} is NaN`, { index: i });
|
|
609
|
+
}
|
|
610
|
+
const back = ids.indexOf(id);
|
|
611
|
+
if (back !== i) {
|
|
612
|
+
throw invariantViolation("I11", `ids.indexOf(ids.idOf(${i})) is ${back}`, { index: i, found: back });
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* The E_INVALID_SNAPSHOT error of one column length-rule violation (I12).
|
|
618
|
+
* @param table - the table name for the message
|
|
619
|
+
* @param column - the column
|
|
620
|
+
* @param message - what is wrong
|
|
621
|
+
* @param details - extra location details
|
|
622
|
+
* @returns the error
|
|
623
|
+
*/
|
|
624
|
+
function columnViolation(table, column, message, details = {}) {
|
|
625
|
+
return invariantViolation("I12", `column "${column.meta.name}" of table ${table}: ${message}`, {
|
|
626
|
+
table,
|
|
627
|
+
column: column.meta.name,
|
|
628
|
+
...details,
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Check that offsets are rows + 1 long, non-decreasing and end at `end`.
|
|
633
|
+
* @param table - the table name for the message
|
|
634
|
+
* @param column - the column
|
|
635
|
+
* @param offsets - the offsets
|
|
636
|
+
* @param rows - the row count
|
|
637
|
+
* @param end - the expected last offset
|
|
638
|
+
*/
|
|
639
|
+
function checkOffsets(table, column, offsets, rows, end) {
|
|
640
|
+
if (offsets.length !== rows + 1) {
|
|
641
|
+
throw columnViolation(table, column, `offsets has ${offsets.length} entries, expected ${rows + 1}`, {
|
|
642
|
+
expected: rows + 1,
|
|
643
|
+
found: offsets.length,
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
if (offsets[0] !== 0) {
|
|
647
|
+
throw columnViolation(table, column, `offsets[0] is ${offsets[0]}`, { row: 0 });
|
|
648
|
+
}
|
|
649
|
+
for (let r = 0; r < rows; r++) {
|
|
650
|
+
if (offsets[r + 1] < offsets[r]) {
|
|
651
|
+
throw columnViolation(table, column, `offsets decrease at row ${r}`, { row: r });
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
if (offsets[rows] !== end) {
|
|
655
|
+
throw columnViolation(table, column, `offsets[${rows}] is ${offsets[rows]}, expected ${end}`, {
|
|
656
|
+
row: rows,
|
|
657
|
+
found: offsets[rows],
|
|
658
|
+
expected: end,
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Check the values of a refersTo u32 array: every entry below `bound` or INVALID_INDEX; when
|
|
664
|
+
* `rowValidity` is given an INVALID_INDEX entry requires its row to be unset.
|
|
665
|
+
* @param table - the table name for the message
|
|
666
|
+
* @param column - the column
|
|
667
|
+
* @param values - the u32 values
|
|
668
|
+
* @param components - values per row
|
|
669
|
+
* @param bound - the referenced space's row count
|
|
670
|
+
* @param rowValidity - the validity bitmap of the rows (null = all set), or undefined to skip the unset rule
|
|
671
|
+
*/
|
|
672
|
+
function checkReferences(table, column, values, components, bound, rowValidity) {
|
|
673
|
+
for (let i = 0; i < values.length; i++) {
|
|
674
|
+
const value = values[i];
|
|
675
|
+
if (value === INVALID_INDEX) {
|
|
676
|
+
const row = Math.floor(i / components);
|
|
677
|
+
if (rowValidity !== undefined && (rowValidity === null || bitmapGet(rowValidity, row))) {
|
|
678
|
+
throw columnViolation(table, column, `row ${row} is set but holds INVALID_INDEX`, { row });
|
|
679
|
+
}
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
if (value >= bound) {
|
|
683
|
+
throw columnViolation(table, column, `value ${value} at ${i} is not below ${bound}`, {
|
|
684
|
+
row: Math.floor(i / components),
|
|
685
|
+
found: value,
|
|
686
|
+
expected: bound,
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* The refersTo rules of invariant I12 for one column: a u32 column's values (or a list column's u32
|
|
693
|
+
* child items) are below `bound` or INVALID_INDEX, and a scalar INVALID_INDEX sits in an unset row.
|
|
694
|
+
* @param table - the table name for the message
|
|
695
|
+
* @param column - a column whose meta.refersTo is set
|
|
696
|
+
* @param bound - the referenced space's row count
|
|
697
|
+
*/
|
|
698
|
+
function checkColumnReferences(table, column, bound) {
|
|
699
|
+
switch (column.dtype) {
|
|
700
|
+
case "u32":
|
|
701
|
+
checkReferences(table, column, column.data, column.meta.components, bound, column.validity);
|
|
702
|
+
return;
|
|
703
|
+
case "list": {
|
|
704
|
+
const { child } = column;
|
|
705
|
+
if (child.dtype !== "u32") {
|
|
706
|
+
throw columnViolation(table, column, `refersTo list requires a u32 child, found ${child.dtype}`);
|
|
707
|
+
}
|
|
708
|
+
checkReferences(table, column, child.data, 1, bound, undefined);
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
default:
|
|
712
|
+
throw columnViolation(table, column, `refersTo requires dtype u32, found ${column.dtype}`);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* The typed arrays a column exposes to a GPU consumer (data, validity, codes, list child data): each
|
|
717
|
+
* must be a view over a plain ArrayBuffer (I10, decision D-SAB).
|
|
718
|
+
* @param column - the column
|
|
719
|
+
* @returns the arrays with their names
|
|
720
|
+
*/
|
|
721
|
+
function columnArrays(column) {
|
|
722
|
+
switch (column.dtype) {
|
|
723
|
+
case "f32":
|
|
724
|
+
case "f64":
|
|
725
|
+
case "i32":
|
|
726
|
+
case "u32":
|
|
727
|
+
case "u8":
|
|
728
|
+
case "bool":
|
|
729
|
+
return [
|
|
730
|
+
["data", column.data],
|
|
731
|
+
["validity", column.validity],
|
|
732
|
+
];
|
|
733
|
+
case "dict":
|
|
734
|
+
return [
|
|
735
|
+
["codes", column.codes],
|
|
736
|
+
["validity", column.validity],
|
|
737
|
+
];
|
|
738
|
+
case "string":
|
|
739
|
+
return [
|
|
740
|
+
["offsets", column.offsets],
|
|
741
|
+
["utf8", column.utf8],
|
|
742
|
+
["validity", column.validity],
|
|
743
|
+
];
|
|
744
|
+
case "list":
|
|
745
|
+
return [
|
|
746
|
+
["offsets", column.offsets],
|
|
747
|
+
["validity", column.validity],
|
|
748
|
+
...columnArrays(column.child).map(([name, array]) => [
|
|
749
|
+
`child.${name}`,
|
|
750
|
+
array,
|
|
751
|
+
]),
|
|
752
|
+
];
|
|
753
|
+
case "json":
|
|
754
|
+
return [["validity", column.validity]];
|
|
755
|
+
default: {
|
|
756
|
+
const unknown = column;
|
|
757
|
+
throw invariantViolation("I12", `unknown dtype ${unknown.dtype}`);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* The length rules of design section 5.7 for one column, plus dictionary code ranges, refersTo
|
|
763
|
+
* ranges, the recorded nullCount (recomputed from the bitmap, design section 9.5) and the plain
|
|
764
|
+
* ArrayBuffer rule of I10 for every typed array the column exposes (I12).
|
|
765
|
+
* @param table - the table name for the message
|
|
766
|
+
* @param column - the column
|
|
767
|
+
* @param rows - the table's row count
|
|
768
|
+
* @param s - the snapshot (for the referenced row counts)
|
|
769
|
+
*/
|
|
770
|
+
export function checkColumnRules(table, column, rows, s) {
|
|
771
|
+
if (column.length !== rows) {
|
|
772
|
+
throw columnViolation(table, column, `has ${column.length} rows, expected ${rows}`, {
|
|
773
|
+
expected: rows,
|
|
774
|
+
found: column.length,
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
const { validity, meta } = column;
|
|
778
|
+
const validityWords = Math.ceil(rows / 32);
|
|
779
|
+
if (validity !== null && validity.length !== validityWords) {
|
|
780
|
+
throw columnViolation(table, column, `validity has ${validity.length} words, expected ${validityWords}`, {
|
|
781
|
+
expected: validityWords,
|
|
782
|
+
found: validity.length,
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
const nullCount = validity === null ? 0 : rows - bitmapCount(validity, rows);
|
|
786
|
+
if (column.nullCount !== nullCount) {
|
|
787
|
+
throw columnViolation(table, column, `nullCount is ${column.nullCount}, the validity bitmap says ${nullCount}`, {
|
|
788
|
+
expected: nullCount,
|
|
789
|
+
found: column.nullCount,
|
|
790
|
+
reason: "nullCount",
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
for (const [name, array] of columnArrays(column)) {
|
|
794
|
+
if (array !== null && !isOverPlainBuffer(array)) {
|
|
795
|
+
throw columnViolation(table, column, `${name} is not a view over a plain ArrayBuffer`, {
|
|
796
|
+
array: name,
|
|
797
|
+
reason: "buffer",
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
const refBound = meta.refersTo === "node" ? s.nodeCount : s.edgeCount;
|
|
802
|
+
const { dtype } = column;
|
|
803
|
+
switch (dtype) {
|
|
804
|
+
case "f32":
|
|
805
|
+
case "f64":
|
|
806
|
+
case "i32":
|
|
807
|
+
case "u32":
|
|
808
|
+
case "u8": {
|
|
809
|
+
const expected = rows * meta.components;
|
|
810
|
+
if (column.data.length !== expected) {
|
|
811
|
+
throw columnViolation(table, column, `data has ${column.data.length} values, expected ${expected}`, {
|
|
812
|
+
expected,
|
|
813
|
+
found: column.data.length,
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
if (meta.refersTo !== null) {
|
|
817
|
+
checkColumnReferences(table, column, refBound);
|
|
818
|
+
}
|
|
819
|
+
break;
|
|
820
|
+
}
|
|
821
|
+
case "bool":
|
|
822
|
+
if (column.data.length !== validityWords) {
|
|
823
|
+
throw columnViolation(table, column, `bool data has ${column.data.length} words, expected ${validityWords}`, {
|
|
824
|
+
expected: validityWords,
|
|
825
|
+
found: column.data.length,
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
break;
|
|
829
|
+
case "dict": {
|
|
830
|
+
if (column.codes.length !== rows) {
|
|
831
|
+
throw columnViolation(table, column, `codes has ${column.codes.length} entries, expected ${rows}`, {
|
|
832
|
+
expected: rows,
|
|
833
|
+
found: column.codes.length,
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
const size = column.dictionary.length;
|
|
837
|
+
for (let r = 0; r < rows; r++) {
|
|
838
|
+
if (column.codes[r] >= size && (validity === null || bitmapGet(validity, r))) {
|
|
839
|
+
throw columnViolation(table, column, `code ${column.codes[r]} at row ${r} is not below ${size}`, {
|
|
840
|
+
row: r,
|
|
841
|
+
found: column.codes[r],
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
break;
|
|
846
|
+
}
|
|
847
|
+
case "string":
|
|
848
|
+
checkOffsets(table, column, column.offsets, rows, column.utf8.length);
|
|
849
|
+
break;
|
|
850
|
+
case "list": {
|
|
851
|
+
const { child } = column;
|
|
852
|
+
checkOffsets(table, column, column.offsets, rows, child.length);
|
|
853
|
+
if (child.validity !== null) {
|
|
854
|
+
throw columnViolation(table, column, "list child column has a validity bitmap");
|
|
855
|
+
}
|
|
856
|
+
if (meta.refersTo !== null) {
|
|
857
|
+
checkColumnReferences(table, column, refBound);
|
|
858
|
+
}
|
|
859
|
+
break;
|
|
860
|
+
}
|
|
861
|
+
case "json":
|
|
862
|
+
if (column.values.length !== rows) {
|
|
863
|
+
throw columnViolation(table, column, `values has ${column.values.length} entries, expected ${rows}`, {
|
|
864
|
+
expected: rows,
|
|
865
|
+
found: column.values.length,
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
break;
|
|
869
|
+
default: {
|
|
870
|
+
const unknown = dtype;
|
|
871
|
+
throw columnViolation(table, column, `unknown dtype ${String(unknown)}`);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* Check every column of a table against its row count.
|
|
877
|
+
* @param name - the table name for the message
|
|
878
|
+
* @param table - the table
|
|
879
|
+
* @param rows - the expected row count
|
|
880
|
+
* @param s - the snapshot
|
|
881
|
+
*/
|
|
882
|
+
function checkTable(name, table, rows, s) {
|
|
883
|
+
for (const column of table) {
|
|
884
|
+
checkColumnRules(name, column, rows, s);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* I12: the node, edge and graph tables have nodeCount, edgeCount and 1 rows, every column (extension
|
|
889
|
+
* tables included) obeys its length rules, dictionary codes are in range and refersTo values are in
|
|
890
|
+
* range or INVALID_INDEX with the row unset.
|
|
891
|
+
* @param s - the snapshot
|
|
892
|
+
*/
|
|
893
|
+
export function checkI12(s) {
|
|
894
|
+
const tables = [
|
|
895
|
+
["nodes", s.nodes, s.nodeCount],
|
|
896
|
+
["edges", s.edges, s.edgeCount],
|
|
897
|
+
["graph", s.graph, 1],
|
|
898
|
+
];
|
|
899
|
+
for (const [name, table, rows] of tables) {
|
|
900
|
+
if (table.rowCount !== rows) {
|
|
901
|
+
throw invariantViolation("I12", `table ${name} has ${table.rowCount} rows, expected ${rows}`, {
|
|
902
|
+
table: name,
|
|
903
|
+
expected: rows,
|
|
904
|
+
found: table.rowCount,
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
checkTable(name, table, rows, s);
|
|
908
|
+
}
|
|
909
|
+
for (const [name, table] of s.extensions) {
|
|
910
|
+
checkTable(`extensions[${name}]`, table, table.rowCount, s);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* I13: edge attribute columns are indexed by logical edge: the edge table has edgeCount rows.
|
|
915
|
+
* @param s - the snapshot
|
|
916
|
+
*/
|
|
917
|
+
export function checkI13(s) {
|
|
918
|
+
if (s.edges.rowCount !== s.edgeCount) {
|
|
919
|
+
throw invariantViolation("I13", `edge table has ${s.edges.rowCount} rows, expected edgeCount ${s.edgeCount}`, {
|
|
920
|
+
expected: s.edgeCount,
|
|
921
|
+
found: s.edges.rowCount,
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
// ============================================================ levels
|
|
926
|
+
/**
|
|
927
|
+
* The "structure" level of design section 9.5: I1-I3, I5 lengths and ranges, I6 / I7 counts, I8
|
|
928
|
+
* length, I10, I11 size, I12 lengths and ranges, I13. O(n + m + columns).
|
|
929
|
+
* @param s - the snapshot
|
|
930
|
+
*/
|
|
931
|
+
export function validateStructure(s) {
|
|
932
|
+
checkI3(s);
|
|
933
|
+
checkI1(s);
|
|
934
|
+
checkI2(s);
|
|
935
|
+
checkI5Ranges(s);
|
|
936
|
+
checkI6(s, false);
|
|
937
|
+
checkI7Counts(s);
|
|
938
|
+
checkI8Length(s);
|
|
939
|
+
checkI10(s);
|
|
940
|
+
checkI11Size(s);
|
|
941
|
+
checkI13(s);
|
|
942
|
+
checkI12(s);
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* The "full" level of design section 9.5: structure plus I4, I5 orientation, I6 permutation, I8 NaN
|
|
946
|
+
* (before the pairing walk, so a NaN weight is reported as I8 rather than as an unequal mate), I7
|
|
947
|
+
* pairing, I9 flags, I11 bijection and unique columns. O(m log d).
|
|
948
|
+
* @param s - the snapshot
|
|
949
|
+
*/
|
|
950
|
+
export function validateFull(s) {
|
|
951
|
+
validateStructure(s);
|
|
952
|
+
checkI4(s);
|
|
953
|
+
checkI5Orientation(s);
|
|
954
|
+
checkI6(s, true);
|
|
955
|
+
checkI8NaN(s);
|
|
956
|
+
checkI7Pairing(s);
|
|
957
|
+
checkI9(s);
|
|
958
|
+
checkI11Bijection(s);
|
|
959
|
+
const tables = [
|
|
960
|
+
["nodes", s.nodes],
|
|
961
|
+
["edges", s.edges],
|
|
962
|
+
["graph", s.graph],
|
|
963
|
+
...[...s.extensions].map(([name, table]) => [`extensions[${name}]`, table]),
|
|
964
|
+
];
|
|
965
|
+
for (const [name, table] of tables) {
|
|
966
|
+
for (const column of table) {
|
|
967
|
+
checkUniqueColumn(name, column);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* The `unique` rule of a column as a validation failure (design sections 9.5 and 11.4): the
|
|
973
|
+
* freeze-time codes E_DUPLICATE_ID / E_DUPLICATE_EDGE_ID of `verifyUniqueColumn` become
|
|
974
|
+
* E_INVALID_SNAPSHOT with `details.invariant` "I12", `details.reason` "unique" and the original
|
|
975
|
+
* code in `details.cause`, so a reader of untrusted input sees one error family.
|
|
976
|
+
* @param table - the table name for the message
|
|
977
|
+
* @param column - the column
|
|
978
|
+
*/
|
|
979
|
+
export function checkUniqueColumn(table, column) {
|
|
980
|
+
try {
|
|
981
|
+
verifyUniqueColumn(column);
|
|
982
|
+
}
|
|
983
|
+
catch (err) {
|
|
984
|
+
if (err instanceof GraphFormatError && (err.code === "E_DUPLICATE_ID" || err.code === "E_DUPLICATE_EDGE_ID")) {
|
|
985
|
+
throw columnViolation(table, column, err.message, { ...err.details, reason: "unique", cause: err.code });
|
|
986
|
+
}
|
|
987
|
+
throw err;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
//# sourceMappingURL=validate.js.map
|