@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,506 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two stable counting-sort passes of the freeze pipeline (design section 6.3 steps 3-6, 8): arc
|
|
3
|
+
* materialisation (doubled storage for undirected graphs, self-loops once, invariant I7), the
|
|
4
|
+
* identity check for directed input that is already grouped by source and sorted by target (step 4),
|
|
5
|
+
* pass 1 by target, pass 2 by source into rowPtr with the scatter writing colIdx / arcToEdge /
|
|
6
|
+
* edgeToArc straight into the target arrays (the arena, for a "keep" freeze), and the flag
|
|
7
|
+
* predicates of design section 3.8 computed on the way (I9). There is no comparator sort anywhere;
|
|
8
|
+
* the cost is O(n + A).
|
|
9
|
+
*
|
|
10
|
+
* Because pass 2 is stable and consumes arcs in pass-1 order, every row ends sorted by target with
|
|
11
|
+
* parallel arcs in ascending logical-edge order (I4), and the k-th `u -> v` arc in row u and the
|
|
12
|
+
* k-th `v -> u` arc in row v come from the same edge (design section 6.4).
|
|
13
|
+
*
|
|
14
|
+
* Layout of the passes, chosen from the memory profile of the 100k-node / 1M-edge benchmark (the
|
|
15
|
+
* scatters are cache-miss bound, so the rule is: as few random streams per loop as possible, and no
|
|
16
|
+
* random LOAD whose value a branch depends on inside a scatter):
|
|
17
|
+
*
|
|
18
|
+
* - One sequential pass over the edges counts arcs per target and per source (both prefix sums come
|
|
19
|
+
* out of it), counts the self-loops, and scans the weights (NaN refusal, I8; the min / max of the
|
|
20
|
+
* stored f32 values, from which the three weight flags follow, I9). Every edge lands on at least
|
|
21
|
+
* one arc, so predicates over the edges equal predicates over the arcs.
|
|
22
|
+
* - Pass 1 scatters (source, edge) PAIRS into target order, one 8-byte write per arc into a single
|
|
23
|
+
* `byTarget` transient of 2A words; for an undirected graph a bitmap over the pass-1 positions
|
|
24
|
+
* marks the arc whose stored orientation is the declared one (the mirror arc of an edge is
|
|
25
|
+
* emitted right after it, so arcs stay in edge order within a bucket).
|
|
26
|
+
* - Pass 2 reads `byTarget` sequentially bucket by bucket and scatters ONLY colIdx and arcToEdge (and
|
|
27
|
+
* edgeToArc when undirected, where the declared bit is at hand).
|
|
28
|
+
* - Sequential post-passes derive edgeToArc from arcToEdge (directed), gather the arc weights
|
|
29
|
+
* through arcToEdge, and test adjacent targets within each row for the multigraph flag.
|
|
30
|
+
*
|
|
31
|
+
* Every loop lives in its own small function: V8 optimises a hot loop by on-stack replacement of the
|
|
32
|
+
* function that contains it, and a function invoked once per freeze with a dozen loops in it is
|
|
33
|
+
* re-entered through stale OSR code that deoptimises at every loop exit.
|
|
34
|
+
*
|
|
35
|
+
* Transients of the non-identity path: 2 x U32(n + 1), U32(2A), and for an undirected graph a bitmap
|
|
36
|
+
* of A bits.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import { GraphFormatError } from "../errors.js";
|
|
40
|
+
import { type F32, type F64, type SnapshotFlags, type U32 } from "../types/index.js";
|
|
41
|
+
import { allocateCoreArrays, type CoreArrays } from "./arena.js";
|
|
42
|
+
|
|
43
|
+
/** The per-logical-edge input of the sort: views over the (compacted) staging arrays. */
|
|
44
|
+
export interface SortInput {
|
|
45
|
+
/** Whether the graph is directed. */
|
|
46
|
+
readonly directed: boolean;
|
|
47
|
+
/** The node count n; every endpoint is below it. */
|
|
48
|
+
readonly nodeCount: number;
|
|
49
|
+
/** The logical edge count E; only the first E entries of src / dst / weights are read. */
|
|
50
|
+
readonly edgeCount: number;
|
|
51
|
+
/** Declared source of every edge. */
|
|
52
|
+
readonly src: U32;
|
|
53
|
+
/** Declared target of every edge. */
|
|
54
|
+
readonly dst: U32;
|
|
55
|
+
/** Per-edge staging weights (f32 or f64), or null when unweighted. */
|
|
56
|
+
readonly weights: F32 | F64 | null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** What the sort produces: the core arrays, the counts and the truthful flags. */
|
|
60
|
+
export interface SortResult {
|
|
61
|
+
/** The core arrays (in the arena when requested). */
|
|
62
|
+
readonly core: CoreArrays;
|
|
63
|
+
/** colIdx.length. */
|
|
64
|
+
readonly arcCount: number;
|
|
65
|
+
/** Logical edges with source === target. */
|
|
66
|
+
readonly selfLoopCount: number;
|
|
67
|
+
/** The flags of design section 3.8, computed from the arrays (invariant I9). */
|
|
68
|
+
readonly flags: SnapshotFlags;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The number of self-loop edges among the first `edgeCount` entries.
|
|
73
|
+
* @param src - sources
|
|
74
|
+
* @param dst - targets
|
|
75
|
+
* @param edgeCount - how many edges to inspect
|
|
76
|
+
* @returns the loop count
|
|
77
|
+
*/
|
|
78
|
+
export function countSelfLoops(src: U32, dst: U32, edgeCount: number): number {
|
|
79
|
+
let loops = 0;
|
|
80
|
+
for (let e = 0; e < edgeCount; e++) {
|
|
81
|
+
if (src[e] === dst[e]) {
|
|
82
|
+
loops++;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return loops;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The identity check of design section 6.3 step 4: whether (src[e], dst[e]) is non-decreasing
|
|
90
|
+
* lexicographically, so that a stable sort by (source, target) moves nothing and the CSR permutation is
|
|
91
|
+
* the identity.
|
|
92
|
+
* @param src - sources
|
|
93
|
+
* @param dst - targets
|
|
94
|
+
* @param edgeCount - how many edges to inspect
|
|
95
|
+
* @returns true when the edges are already in CSR order
|
|
96
|
+
*/
|
|
97
|
+
export function isSortedEdgeList(src: U32, dst: U32, edgeCount: number): boolean {
|
|
98
|
+
for (let e = 1; e < edgeCount; e++) {
|
|
99
|
+
const u = src[e];
|
|
100
|
+
const prev = src[e - 1];
|
|
101
|
+
if (u < prev || (u === prev && dst[e] < dst[e - 1])) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The E_INVALID_WEIGHT error for a NaN staging weight (invariant I8).
|
|
110
|
+
* @param e - the logical edge
|
|
111
|
+
* @param w - the value
|
|
112
|
+
* @returns the error
|
|
113
|
+
*/
|
|
114
|
+
function nanWeight(e: number, w: number): GraphFormatError {
|
|
115
|
+
return new GraphFormatError("E_INVALID_WEIGHT", `edge ${e} has a NaN weight`, { edge: e, weight: w });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ============================================================ the sequential edge pass
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The weight scan of design section 6.3 step 8 over the first `edgeCount` staging weights: NaN is
|
|
122
|
+
* refused (invariant I8, the bulk-input re-check, naming the lowest such edge), and the smallest and
|
|
123
|
+
* largest STORED values -- the values as the f32 arc array holds them (invariant I9: an f64 that
|
|
124
|
+
* rounds to 1, to -0 or to Infinity counts as its rounded value) -- are left in `bounds[0]` and
|
|
125
|
+
* `bounds[1]`. They are written from inside the loop whenever they change, so the loop's optimised
|
|
126
|
+
* code has no exit path without type feedback.
|
|
127
|
+
* @param edgeWeights - the staging weights
|
|
128
|
+
* @param edgeCount - how many edges to inspect; at least 1
|
|
129
|
+
* @param bounds - two slots initialised to +Infinity / -Infinity; receive the minimum and the maximum
|
|
130
|
+
*/
|
|
131
|
+
function scanStoredWeights(edgeWeights: F32 | F64, edgeCount: number, bounds: F64): void {
|
|
132
|
+
let min = Infinity;
|
|
133
|
+
let max = -Infinity;
|
|
134
|
+
for (let e = 0; e < edgeCount; e++) {
|
|
135
|
+
const w = Math.fround(edgeWeights[e]);
|
|
136
|
+
if (w !== w) {
|
|
137
|
+
throw nanWeight(e, edgeWeights[e]);
|
|
138
|
+
}
|
|
139
|
+
if (w < min) {
|
|
140
|
+
min = w;
|
|
141
|
+
bounds[0] = w;
|
|
142
|
+
}
|
|
143
|
+
if (w > max) {
|
|
144
|
+
max = w;
|
|
145
|
+
bounds[1] = w;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* The counting half of the directed sort (design section 6.3 steps 5 and 6, the `cnt` arrays):
|
|
152
|
+
* arcs per target into `targetCount[v + 1]` and arcs per source into `rowCount[u + 1]`, ready for the
|
|
153
|
+
* exclusive prefix sums.
|
|
154
|
+
* @param src - sources
|
|
155
|
+
* @param dst - targets
|
|
156
|
+
* @param edgeCount - the logical edge count
|
|
157
|
+
* @param targetCount - n + 1 zeroed counters, by target
|
|
158
|
+
* @param rowCount - n + 1 zeroed counters, by source
|
|
159
|
+
* @returns the self-loop count
|
|
160
|
+
*/
|
|
161
|
+
function countDirectedArcs(src: U32, dst: U32, edgeCount: number, targetCount: U32, rowCount: U32): number {
|
|
162
|
+
let loops = 0;
|
|
163
|
+
for (let e = 0; e < edgeCount; e++) {
|
|
164
|
+
const u = src[e];
|
|
165
|
+
const v = dst[e];
|
|
166
|
+
targetCount[v + 1]++;
|
|
167
|
+
rowCount[u + 1]++;
|
|
168
|
+
if (u === v) {
|
|
169
|
+
loops++;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return loops;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The counting half of the undirected sort (design section 6.3 steps 3, 5 and 6): every non-loop
|
|
177
|
+
* edge contributes an arc in each direction, a self-loop one arc (invariant I7).
|
|
178
|
+
* @param src - sources
|
|
179
|
+
* @param dst - targets
|
|
180
|
+
* @param edgeCount - the logical edge count
|
|
181
|
+
* @param targetCount - n + 1 zeroed counters, by target
|
|
182
|
+
* @param rowCount - n + 1 zeroed counters, by source
|
|
183
|
+
* @returns the self-loop count
|
|
184
|
+
*/
|
|
185
|
+
function countUndirectedArcs(src: U32, dst: U32, edgeCount: number, targetCount: U32, rowCount: U32): number {
|
|
186
|
+
let loops = 0;
|
|
187
|
+
for (let e = 0; e < edgeCount; e++) {
|
|
188
|
+
const u = src[e];
|
|
189
|
+
const v = dst[e];
|
|
190
|
+
targetCount[v + 1]++;
|
|
191
|
+
rowCount[u + 1]++;
|
|
192
|
+
if (u === v) {
|
|
193
|
+
loops++;
|
|
194
|
+
} else {
|
|
195
|
+
targetCount[u + 1]++;
|
|
196
|
+
rowCount[v + 1]++;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return loops;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Turn per-node counts stored at index + 1 into exclusive prefix sums in place: afterwards
|
|
204
|
+
* `counts[v]` is the start of v's bucket and `counts[nodeCount]` the total.
|
|
205
|
+
* @param counts - n + 1 counters with `counts[0] === 0`
|
|
206
|
+
* @param nodeCount - n
|
|
207
|
+
*/
|
|
208
|
+
function prefixSum(counts: U32, nodeCount: number): void {
|
|
209
|
+
for (let v = 0; v < nodeCount; v++) {
|
|
210
|
+
counts[v + 1] += counts[v];
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ============================================================ the identity path
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* The identity path of design section 6.3 step 4 (directed input already in CSR order): colIdx is a
|
|
218
|
+
* copy of dst, and the multigraph flag is one comparison of adjacent edges.
|
|
219
|
+
* @param src - sources, grouped
|
|
220
|
+
* @param dst - targets, sorted within each group
|
|
221
|
+
* @param edgeCount - the logical edge count
|
|
222
|
+
* @param colIdx - receives dst
|
|
223
|
+
* @returns true when two adjacent edges connect the same pair
|
|
224
|
+
*/
|
|
225
|
+
function copySortedTargets(src: U32, dst: U32, edgeCount: number, colIdx: U32): boolean {
|
|
226
|
+
let multigraph = false;
|
|
227
|
+
for (let e = 0; e < edgeCount; e++) {
|
|
228
|
+
const v = dst[e];
|
|
229
|
+
colIdx[e] = v;
|
|
230
|
+
if (e > 0 && src[e] === src[e - 1] && dst[e - 1] === v) {
|
|
231
|
+
multigraph = true;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return multigraph;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ============================================================ pass 1: by target
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Pass 1 of a directed sort (design section 6.3 step 5): scatter every edge into target order as a
|
|
241
|
+
* (source, edge) pair, `byTarget[2p] = src[e]`, `byTarget[2p + 1] = e`.
|
|
242
|
+
* @param src - sources
|
|
243
|
+
* @param dst - targets
|
|
244
|
+
* @param edgeCount - the logical edge count
|
|
245
|
+
* @param cursor - the target bucket starts; advanced in place
|
|
246
|
+
* @param byTarget - 2A words receiving the pairs
|
|
247
|
+
*/
|
|
248
|
+
function scatterDirectedByTarget(src: U32, dst: U32, edgeCount: number, cursor: U32, byTarget: U32): void {
|
|
249
|
+
for (let e = 0; e < edgeCount; e++) {
|
|
250
|
+
const p = 2 * cursor[dst[e]]++;
|
|
251
|
+
byTarget[p] = src[e];
|
|
252
|
+
byTarget[p + 1] = e;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Pass 1 of an undirected sort (design section 6.3 steps 3 and 5, fused): the declared arc
|
|
258
|
+
* `(src, dst, e)` goes into bucket dst and, for a non-loop, the mirror `(dst, src, e)` into bucket
|
|
259
|
+
* src right after it, so arcs stay in edge order within every bucket. The pass-1 position of every
|
|
260
|
+
* declared arc is marked in `declared` (bit p).
|
|
261
|
+
* @param src - sources
|
|
262
|
+
* @param dst - targets
|
|
263
|
+
* @param edgeCount - the logical edge count
|
|
264
|
+
* @param cursor - the target bucket starts; advanced in place
|
|
265
|
+
* @param byTarget - 2A words receiving the (source, edge) pairs
|
|
266
|
+
* @param declared - a zeroed bitmap of A bits
|
|
267
|
+
*/
|
|
268
|
+
function scatterUndirectedByTarget(
|
|
269
|
+
src: U32,
|
|
270
|
+
dst: U32,
|
|
271
|
+
edgeCount: number,
|
|
272
|
+
cursor: U32,
|
|
273
|
+
byTarget: U32,
|
|
274
|
+
declared: U32,
|
|
275
|
+
): void {
|
|
276
|
+
for (let e = 0; e < edgeCount; e++) {
|
|
277
|
+
const u = src[e];
|
|
278
|
+
const v = dst[e];
|
|
279
|
+
const p = cursor[v]++;
|
|
280
|
+
byTarget[2 * p] = u;
|
|
281
|
+
byTarget[2 * p + 1] = e;
|
|
282
|
+
declared[p >>> 5] |= 1 << (p & 31);
|
|
283
|
+
if (u !== v) {
|
|
284
|
+
const q = 2 * cursor[u]++;
|
|
285
|
+
byTarget[q] = v;
|
|
286
|
+
byTarget[q + 1] = e;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ============================================================ pass 2: by source
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Pass 2 of a directed sort (design section 6.3 step 6): walk the target buckets in order and
|
|
295
|
+
* scatter every arc into its row, writing colIdx and arcToEdge. Stable, so rows end sorted by target
|
|
296
|
+
* with parallels in edge order (I4).
|
|
297
|
+
* @param nodeCount - n
|
|
298
|
+
* @param targetStart - the target bucket starts (pass-1 layout)
|
|
299
|
+
* @param byTarget - the pass-1 (source, edge) pairs
|
|
300
|
+
* @param cursor - the row starts (a copy of rowPtr); advanced in place
|
|
301
|
+
* @param colIdx - receives the targets
|
|
302
|
+
* @param arcToEdge - receives the edges
|
|
303
|
+
*/
|
|
304
|
+
function scatterDirectedBySource(
|
|
305
|
+
nodeCount: number,
|
|
306
|
+
targetStart: U32,
|
|
307
|
+
byTarget: U32,
|
|
308
|
+
cursor: U32,
|
|
309
|
+
colIdx: U32,
|
|
310
|
+
arcToEdge: U32,
|
|
311
|
+
): void {
|
|
312
|
+
for (let v = 0; v < nodeCount; v++) {
|
|
313
|
+
const end = 2 * targetStart[v + 1];
|
|
314
|
+
for (let p = 2 * targetStart[v]; p < end; p += 2) {
|
|
315
|
+
const a = cursor[byTarget[p]]++;
|
|
316
|
+
colIdx[a] = v;
|
|
317
|
+
arcToEdge[a] = byTarget[p + 1];
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Pass 2 of an undirected sort (design section 6.3 step 6): as the directed pass, plus
|
|
324
|
+
* `edgeToArc[e] = a` for the arc whose stored orientation is the declared one (for a self-loop the
|
|
325
|
+
* single arc), which the pass-1 bitmap identifies.
|
|
326
|
+
* @param nodeCount - n
|
|
327
|
+
* @param targetStart - the target bucket starts (pass-1 layout)
|
|
328
|
+
* @param byTarget - the pass-1 (source, edge) pairs
|
|
329
|
+
* @param declared - bit p set when pass-1 position p holds a declared arc
|
|
330
|
+
* @param cursor - the row starts (a copy of rowPtr); advanced in place
|
|
331
|
+
* @param colIdx - receives the targets
|
|
332
|
+
* @param arcToEdge - receives the edges
|
|
333
|
+
* @param edgeToArc - receives the declared arc of every edge
|
|
334
|
+
*/
|
|
335
|
+
function scatterUndirectedBySource(
|
|
336
|
+
nodeCount: number,
|
|
337
|
+
targetStart: U32,
|
|
338
|
+
byTarget: U32,
|
|
339
|
+
declared: U32,
|
|
340
|
+
cursor: U32,
|
|
341
|
+
colIdx: U32,
|
|
342
|
+
arcToEdge: U32,
|
|
343
|
+
edgeToArc: U32,
|
|
344
|
+
): void {
|
|
345
|
+
for (let v = 0; v < nodeCount; v++) {
|
|
346
|
+
const end = targetStart[v + 1];
|
|
347
|
+
for (let p = targetStart[v]; p < end; p++) {
|
|
348
|
+
const e = byTarget[2 * p + 1];
|
|
349
|
+
const a = cursor[byTarget[2 * p]]++;
|
|
350
|
+
colIdx[a] = v;
|
|
351
|
+
arcToEdge[a] = e;
|
|
352
|
+
if (((declared[p >>> 5] >>> (p & 31)) & 1) === 1) {
|
|
353
|
+
edgeToArc[e] = a;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// ============================================================ sequential post-passes
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* The inverse of a directed permutation: `edgeToArc[arcToEdge[a]] = a` (every arc is its edge's only
|
|
363
|
+
* arc).
|
|
364
|
+
* @param arcToEdge - the arc permutation
|
|
365
|
+
* @param edgeToArc - receives the inverse
|
|
366
|
+
*/
|
|
367
|
+
function invertPermutation(arcToEdge: U32, edgeToArc: U32): void {
|
|
368
|
+
const arcCount = arcToEdge.length;
|
|
369
|
+
for (let a = 0; a < arcCount; a++) {
|
|
370
|
+
edgeToArc[arcToEdge[a]] = a;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* The arc weights (design section 3.7): `weights[a] = f32(edgeWeights[arcToEdge[a]])`, a sequential
|
|
376
|
+
* write with one independent random read per arc.
|
|
377
|
+
* @param arcToEdge - the arc permutation
|
|
378
|
+
* @param edgeWeights - the staging weights
|
|
379
|
+
* @param weights - receives the f32 arc weights
|
|
380
|
+
*/
|
|
381
|
+
function gatherWeights(arcToEdge: U32, edgeWeights: F32 | F64, weights: F32): void {
|
|
382
|
+
const arcCount = arcToEdge.length;
|
|
383
|
+
for (let a = 0; a < arcCount; a++) {
|
|
384
|
+
weights[a] = edgeWeights[arcToEdge[a]];
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* The multigraph flag (design section 3.8): whether some row holds two adjacent arcs with the same
|
|
390
|
+
* target; rows are sorted by target, so parallels are adjacent (I4).
|
|
391
|
+
* @param rowPtr - the row starts
|
|
392
|
+
* @param colIdx - the sorted targets
|
|
393
|
+
* @param nodeCount - n
|
|
394
|
+
* @returns true when a parallel pair exists
|
|
395
|
+
*/
|
|
396
|
+
function hasAdjacentParallels(rowPtr: U32, colIdx: U32, nodeCount: number): boolean {
|
|
397
|
+
for (let u = 0; u < nodeCount; u++) {
|
|
398
|
+
const end = rowPtr[u + 1];
|
|
399
|
+
for (let a = rowPtr[u] + 1; a < end; a++) {
|
|
400
|
+
if (colIdx[a] === colIdx[a - 1]) {
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// ============================================================ the sort
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Sort a per-edge list into a CSR core (design section 6.3 steps 3-6 and 8). Directed input that is
|
|
412
|
+
* already in CSR order takes the identity path (one count + scan + copy, no permutation arrays,
|
|
413
|
+
* `flags.arcToEdgeIsIdentity`); everything else takes the two counting-sort passes. Undirected input
|
|
414
|
+
* emits the mirror arc of every non-loop edge (I7). Weights are stored as f32 (design section 3.7) and
|
|
415
|
+
* the flags are computed from the stored values (I9). A NaN weight is E_INVALID_WEIGHT (I8) naming
|
|
416
|
+
* the lowest such edge, thrown before anything is allocated.
|
|
417
|
+
* @param input - the per-edge arrays
|
|
418
|
+
* @param useArena - allocate the core inside one 256-aligned arena (true) or as separate buffers
|
|
419
|
+
* @returns the core, the counts and the flags
|
|
420
|
+
*/
|
|
421
|
+
export function sortIntoCore(input: SortInput, useArena: boolean): SortResult {
|
|
422
|
+
const { directed, nodeCount, edgeCount, src, dst } = input;
|
|
423
|
+
const edgeWeights = input.weights;
|
|
424
|
+
|
|
425
|
+
// step 8's weight predicates, over the edges: every edge lands on at least one arc, so the
|
|
426
|
+
// per-value predicates over the stored arc weights equal those over the f32-rounded edge weights
|
|
427
|
+
let allOne = true;
|
|
428
|
+
let nonNegative = true;
|
|
429
|
+
let finite = true;
|
|
430
|
+
if (edgeWeights !== null && edgeCount > 0) {
|
|
431
|
+
const bounds = new Float64Array([Infinity, -Infinity]);
|
|
432
|
+
scanStoredWeights(edgeWeights, edgeCount, bounds);
|
|
433
|
+
const min = bounds[0];
|
|
434
|
+
const max = bounds[1];
|
|
435
|
+
allOne = min === 1 && max === 1;
|
|
436
|
+
nonNegative = min >= 0;
|
|
437
|
+
finite = min > -Infinity && max < Infinity;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// steps 3-6 counts: one sequential pass over the edges yields both prefix sums and the loop count
|
|
441
|
+
const targetStart = new Uint32Array(nodeCount + 1);
|
|
442
|
+
const rowCount = new Uint32Array(nodeCount + 1);
|
|
443
|
+
const selfLoopCount = directed
|
|
444
|
+
? countDirectedArcs(src, dst, edgeCount, targetStart, rowCount)
|
|
445
|
+
: countUndirectedArcs(src, dst, edgeCount, targetStart, rowCount);
|
|
446
|
+
const arcCount = directed ? edgeCount : 2 * edgeCount - selfLoopCount;
|
|
447
|
+
const identity = directed && isSortedEdgeList(src, dst, edgeCount);
|
|
448
|
+
const core = allocateCoreArrays(
|
|
449
|
+
{ nodeCount, arcCount, edgeCount, weighted: edgeWeights !== null, identity },
|
|
450
|
+
useArena,
|
|
451
|
+
);
|
|
452
|
+
const { rowPtr, colIdx, weights, arcToEdge, edgeToArc } = core;
|
|
453
|
+
prefixSum(rowCount, nodeCount);
|
|
454
|
+
rowPtr.set(rowCount);
|
|
455
|
+
let multigraph: boolean;
|
|
456
|
+
|
|
457
|
+
if (identity) {
|
|
458
|
+
// step 4: the edge arrays are the arc arrays -- copy, and round the weights to f32
|
|
459
|
+
multigraph = copySortedTargets(src, dst, edgeCount, colIdx);
|
|
460
|
+
if (weights !== null && edgeWeights !== null) {
|
|
461
|
+
weights.set(edgeWeights.subarray(0, edgeCount));
|
|
462
|
+
}
|
|
463
|
+
} else if (arcToEdge !== null && edgeToArc !== null) {
|
|
464
|
+
// step 5: pass 1 by target; `rowCount` is reused as the pass-1 cursor
|
|
465
|
+
prefixSum(targetStart, nodeCount);
|
|
466
|
+
const cursor = rowCount;
|
|
467
|
+
cursor.set(targetStart);
|
|
468
|
+
const byTarget = new Uint32Array(2 * arcCount);
|
|
469
|
+
let declared: U32 | null = null;
|
|
470
|
+
if (directed) {
|
|
471
|
+
scatterDirectedByTarget(src, dst, edgeCount, cursor, byTarget);
|
|
472
|
+
} else {
|
|
473
|
+
declared = new Uint32Array((arcCount + 31) >>> 5);
|
|
474
|
+
scatterUndirectedByTarget(src, dst, edgeCount, cursor, byTarget, declared);
|
|
475
|
+
}
|
|
476
|
+
// step 6: pass 2 by source into the rows; the cursor now walks rowPtr
|
|
477
|
+
cursor.set(rowPtr);
|
|
478
|
+
if (declared === null) {
|
|
479
|
+
scatterDirectedBySource(nodeCount, targetStart, byTarget, cursor, colIdx, arcToEdge);
|
|
480
|
+
invertPermutation(arcToEdge, edgeToArc);
|
|
481
|
+
} else {
|
|
482
|
+
scatterUndirectedBySource(nodeCount, targetStart, byTarget, declared, cursor, colIdx, arcToEdge, edgeToArc);
|
|
483
|
+
}
|
|
484
|
+
if (weights !== null && edgeWeights !== null) {
|
|
485
|
+
gatherWeights(arcToEdge, edgeWeights, weights);
|
|
486
|
+
}
|
|
487
|
+
multigraph = hasAdjacentParallels(rowPtr, colIdx, nodeCount);
|
|
488
|
+
} else {
|
|
489
|
+
// allocateCoreArrays materialises both permutations whenever the sort is not the identity
|
|
490
|
+
throw new GraphFormatError("E_UNSUPPORTED", "unreachable: a non-identity core without permutation arrays", {
|
|
491
|
+
directed,
|
|
492
|
+
arcCount,
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const flags: SnapshotFlags = Object.freeze({
|
|
497
|
+
multigraph,
|
|
498
|
+
hasSelfLoops: selfLoopCount > 0,
|
|
499
|
+
arcToEdgeIsIdentity: identity,
|
|
500
|
+
weighted: weights !== null,
|
|
501
|
+
allWeightsOne: allOne,
|
|
502
|
+
nonNegativeWeights: nonNegative,
|
|
503
|
+
finiteWeights: finite,
|
|
504
|
+
});
|
|
505
|
+
return { core, arcCount, selfLoopCount, flags };
|
|
506
|
+
}
|