@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,943 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The cached views of design section 7.2 as pure functions of a snapshot's core arrays, plus the
|
|
3
|
+
* per-arc / per-edge boundary helpers `foldArcs` and `expandEdges` of design section 7.5.
|
|
4
|
+
*
|
|
5
|
+
* Every function here computes one view from the public core (rowPtr, colIdx, weights, arcToEdge,
|
|
6
|
+
* edgeToArc, flags, counts) in O(n + m) or O(n log d) and returns a fresh object; GraphSnapshot owns
|
|
7
|
+
* the per-instance cache (invariant I17: computed once, never invalidated, SHARED -- a caller that
|
|
8
|
+
* needs scratch calls `.slice()`). Aliasing is deliberate and documented per view: `coo().dst` is
|
|
9
|
+
* `colIdx`, `reverse()` of an undirected snapshot is the forward arrays, an identity permutation
|
|
10
|
+
* lets `edgeList().weights` alias `weights`, and so on, so `viewByteLength()` counts only the bytes a
|
|
11
|
+
* view owns.
|
|
12
|
+
*
|
|
13
|
+
* Nothing here allocates an identity permutation that the snapshot has not materialised: view objects
|
|
14
|
+
* reach `arcToEdge` / `edgeToArc` through getters, so touching `coo()` on a directed identity
|
|
15
|
+
* snapshot costs one `src` array and nothing else until `coo().arcToEdge` is read.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { GraphFormatError } from "../errors.js";
|
|
19
|
+
import {
|
|
20
|
+
type CooView,
|
|
21
|
+
type DegreeOrderView,
|
|
22
|
+
type EdgeListView,
|
|
23
|
+
type F32,
|
|
24
|
+
type F64,
|
|
25
|
+
type GraphSnapshot,
|
|
26
|
+
type NumericVector,
|
|
27
|
+
type ReverseView,
|
|
28
|
+
type U32,
|
|
29
|
+
type ViewName,
|
|
30
|
+
} from "../types/index.js";
|
|
31
|
+
import { assertOneOf } from "../util/options.js";
|
|
32
|
+
import { arcRangeIn } from "./queries.js";
|
|
33
|
+
|
|
34
|
+
// ============================================================ reverse
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The cuGraph degree tiers of `degreeOrder()`: a node with at least DEGREE_TIER_HIGH arcs is scheduled
|
|
38
|
+
* per workgroup, at least DEGREE_TIER_MID per subgroup, at least DEGREE_TIER_LOW per thread; degree 0
|
|
39
|
+
* nodes form the trailing segment (design section 7.2).
|
|
40
|
+
*/
|
|
41
|
+
export const DEGREE_TIER_HIGH = 1024;
|
|
42
|
+
/** Lower bound of the middle degree tier. */
|
|
43
|
+
export const DEGREE_TIER_MID = 32;
|
|
44
|
+
/** Lower bound of the low degree tier. */
|
|
45
|
+
const DEGREE_TIER_LOW = 1;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The identity permutation of `length` entries as a fresh 4-byte-aligned array (invariant I10).
|
|
49
|
+
* @param length - the entry count
|
|
50
|
+
* @returns 0..length-1
|
|
51
|
+
*/
|
|
52
|
+
export function identityPermutation(length: number): U32 {
|
|
53
|
+
const out = new Uint32Array(length);
|
|
54
|
+
for (let i = 0; i < length; i++) {
|
|
55
|
+
out[i] = i;
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The in-adjacency of a snapshot (design section 7.2): `ReverseView` with `fwdArc` (reverse arc k ->
|
|
62
|
+
* forward arc) and `arcToEdge` (`arcToEdge[fwdArc[k]]`) materialised lazily. For an undirected
|
|
63
|
+
* snapshot the forward arrays are shared (invariant I7), `fwdArc` is the identity (allocated on first
|
|
64
|
+
* read) and `arcToEdge` is the snapshot's own array. For a directed snapshot whose permutation is the
|
|
65
|
+
* identity, the reverse `arcToEdge` IS `fwdArc` (aliased, zero bytes).
|
|
66
|
+
*/
|
|
67
|
+
export class ReverseAdjacency implements ReverseView {
|
|
68
|
+
/** Whether the source snapshot is directed. */
|
|
69
|
+
readonly directed: boolean;
|
|
70
|
+
/** Number of nodes. */
|
|
71
|
+
readonly nodeCount: number;
|
|
72
|
+
/** Number of arcs. */
|
|
73
|
+
readonly arcCount: number;
|
|
74
|
+
/** Row offsets of the reverse adjacency (the forward rowPtr when undirected). */
|
|
75
|
+
readonly rowPtr: U32;
|
|
76
|
+
/** Source node of every forward arc, sorted within each reverse row (the forward colIdx when undirected). */
|
|
77
|
+
readonly colIdx: U32;
|
|
78
|
+
/** Per-reverse-arc weights, gathered from the forward weights (the forward array when undirected). */
|
|
79
|
+
readonly weights: F32 | null;
|
|
80
|
+
|
|
81
|
+
private readonly source: GraphSnapshot;
|
|
82
|
+
private fwdArcCache: U32 | null;
|
|
83
|
+
private arcToEdgeCache: U32 | null;
|
|
84
|
+
private arcToEdgeOwned: boolean;
|
|
85
|
+
private readonly fwdArcIsIdentity: boolean;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Wrap the reverse arrays. Used by `computeReverse()`; not meant to be constructed elsewhere.
|
|
89
|
+
* @param source - the snapshot the view belongs to
|
|
90
|
+
* @param rowPtr - the reverse row offsets
|
|
91
|
+
* @param colIdx - the reverse targets (forward sources)
|
|
92
|
+
* @param weights - the gathered weights, or null when unweighted
|
|
93
|
+
* @param fwdArc - the reverse arc -> forward arc map, or null when it is the identity (undirected)
|
|
94
|
+
*/
|
|
95
|
+
constructor(source: GraphSnapshot, rowPtr: U32, colIdx: U32, weights: F32 | null, fwdArc: U32 | null) {
|
|
96
|
+
this.source = source;
|
|
97
|
+
this.directed = source.directed;
|
|
98
|
+
this.nodeCount = source.nodeCount;
|
|
99
|
+
this.arcCount = source.arcCount;
|
|
100
|
+
this.rowPtr = rowPtr;
|
|
101
|
+
this.colIdx = colIdx;
|
|
102
|
+
this.weights = weights;
|
|
103
|
+
this.fwdArcCache = fwdArc;
|
|
104
|
+
this.fwdArcIsIdentity = fwdArc === null;
|
|
105
|
+
this.arcToEdgeCache = null;
|
|
106
|
+
this.arcToEdgeOwned = false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Reverse arc k -> forward arc index; the identity for an undirected snapshot, materialised on first
|
|
111
|
+
* read outside any arena.
|
|
112
|
+
* @returns the map, arcCount entries
|
|
113
|
+
*/
|
|
114
|
+
get fwdArc(): U32 {
|
|
115
|
+
this.fwdArcCache ??= identityPermutation(this.arcCount);
|
|
116
|
+
return this.fwdArcCache;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Logical edge of every reverse arc: `source.arcToEdge[fwdArc[k]]`, materialised on first read.
|
|
121
|
+
* Aliases `source.arcToEdge` when undirected and `fwdArc` when the forward permutation is the
|
|
122
|
+
* identity.
|
|
123
|
+
* @returns the map, arcCount entries
|
|
124
|
+
*/
|
|
125
|
+
get arcToEdge(): U32 {
|
|
126
|
+
if (this.arcToEdgeCache === null) {
|
|
127
|
+
if (this.fwdArcIsIdentity) {
|
|
128
|
+
this.arcToEdgeCache = this.source.arcToEdge;
|
|
129
|
+
} else if (this.source.flags.arcToEdgeIsIdentity) {
|
|
130
|
+
this.arcToEdgeCache = this.fwdArc;
|
|
131
|
+
} else {
|
|
132
|
+
const { fwdArc } = this;
|
|
133
|
+
const forward = this.source.arcToEdge;
|
|
134
|
+
const out = new Uint32Array(this.arcCount);
|
|
135
|
+
for (let k = 0; k < out.length; k++) {
|
|
136
|
+
out[k] = forward[fwdArc[k]];
|
|
137
|
+
}
|
|
138
|
+
this.arcToEdgeCache = out;
|
|
139
|
+
this.arcToEdgeOwned = true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return this.arcToEdgeCache;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Bytes owned by this view (not shared with the snapshot's core): the reverse arrays when directed,
|
|
147
|
+
* a materialised identity `fwdArc`, and a gathered `arcToEdge`.
|
|
148
|
+
* @returns the byte count
|
|
149
|
+
*/
|
|
150
|
+
get ownedByteLength(): number {
|
|
151
|
+
let bytes = 0;
|
|
152
|
+
if (this.directed) {
|
|
153
|
+
bytes += this.rowPtr.byteLength + this.colIdx.byteLength;
|
|
154
|
+
if (this.weights !== null) {
|
|
155
|
+
bytes += this.weights.byteLength;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (this.fwdArcCache !== null) {
|
|
159
|
+
bytes += this.fwdArcCache.byteLength;
|
|
160
|
+
}
|
|
161
|
+
if (this.arcToEdgeOwned && this.arcToEdgeCache !== null) {
|
|
162
|
+
bytes += this.arcToEdgeCache.byteLength;
|
|
163
|
+
}
|
|
164
|
+
return bytes;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* The arrays this view has materialised so far, for checksums and byte accounting.
|
|
169
|
+
* @returns the arrays, keyed by member name
|
|
170
|
+
*/
|
|
171
|
+
materialised(): Readonly<Record<string, Uint32Array | Float32Array>> {
|
|
172
|
+
const out: Record<string, Uint32Array | Float32Array> = {
|
|
173
|
+
rowPtr: this.rowPtr,
|
|
174
|
+
colIdx: this.colIdx,
|
|
175
|
+
};
|
|
176
|
+
if (this.weights !== null) {
|
|
177
|
+
out.weights = this.weights;
|
|
178
|
+
}
|
|
179
|
+
if (this.fwdArcCache !== null) {
|
|
180
|
+
out.fwdArc = this.fwdArcCache;
|
|
181
|
+
}
|
|
182
|
+
if (this.arcToEdgeCache !== null) {
|
|
183
|
+
out.arcToEdge = this.arcToEdgeCache;
|
|
184
|
+
}
|
|
185
|
+
return out;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Compute the reverse view (design section 7.2): a stable counting sort of the forward arcs by target
|
|
191
|
+
* (O(n + m)) so that every reverse row is sorted by source with parallels in ascending edge order
|
|
192
|
+
* (invariant I4 for the reverse), weights gathered when weighted (Q37). Undirected: the forward
|
|
193
|
+
* arrays themselves (invariant I7).
|
|
194
|
+
* @param snapshot - the snapshot
|
|
195
|
+
* @returns the reverse view
|
|
196
|
+
*/
|
|
197
|
+
export function computeReverse(snapshot: GraphSnapshot): ReverseAdjacency {
|
|
198
|
+
const { nodeCount, arcCount, rowPtr, colIdx, weights } = snapshot;
|
|
199
|
+
if (!snapshot.directed) {
|
|
200
|
+
return new ReverseAdjacency(snapshot, rowPtr, colIdx, weights, null);
|
|
201
|
+
}
|
|
202
|
+
const revPtr = new Uint32Array(nodeCount + 1);
|
|
203
|
+
for (let a = 0; a < arcCount; a++) {
|
|
204
|
+
revPtr[colIdx[a] + 1]++;
|
|
205
|
+
}
|
|
206
|
+
for (let v = 0; v < nodeCount; v++) {
|
|
207
|
+
revPtr[v + 1] += revPtr[v];
|
|
208
|
+
}
|
|
209
|
+
const revIdx = new Uint32Array(arcCount);
|
|
210
|
+
const fwdArc = new Uint32Array(arcCount);
|
|
211
|
+
const revWeights = weights === null ? null : new Float32Array(arcCount);
|
|
212
|
+
const cursor = revPtr.slice(0, nodeCount);
|
|
213
|
+
for (let u = 0; u < nodeCount; u++) {
|
|
214
|
+
const end = rowPtr[u + 1];
|
|
215
|
+
for (let a = rowPtr[u]; a < end; a++) {
|
|
216
|
+
const v = colIdx[a];
|
|
217
|
+
const k = cursor[v]++;
|
|
218
|
+
revIdx[k] = u;
|
|
219
|
+
fwdArc[k] = a;
|
|
220
|
+
if (revWeights !== null && weights !== null) {
|
|
221
|
+
revWeights[k] = weights[a];
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return new ReverseAdjacency(snapshot, revPtr, revIdx, revWeights, fwdArc);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ============================================================ coo and edge list
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* The source node of every arc: `rowPtr` expanded to arc length.
|
|
232
|
+
* @param rowPtr - the row offsets
|
|
233
|
+
* @param nodeCount - the node count
|
|
234
|
+
* @param arcCount - the arc count
|
|
235
|
+
* @returns a fresh Uint32Array(arcCount)
|
|
236
|
+
*/
|
|
237
|
+
function expandRowPtr(rowPtr: U32, nodeCount: number, arcCount: number): U32 {
|
|
238
|
+
const src = new Uint32Array(arcCount);
|
|
239
|
+
for (let u = 0; u < nodeCount; u++) {
|
|
240
|
+
const end = rowPtr[u + 1];
|
|
241
|
+
for (let a = rowPtr[u]; a < end; a++) {
|
|
242
|
+
src[a] = u;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return src;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Compute the per-arc COO view (design section 7.2): `src` is the only new array; `dst` aliases
|
|
250
|
+
* `colIdx`, `weights` aliases `weights`, and `arcToEdge` reaches the snapshot's array through a
|
|
251
|
+
* getter (so an identity permutation is not materialised by building the view).
|
|
252
|
+
* @param snapshot - the snapshot
|
|
253
|
+
* @returns the COO view
|
|
254
|
+
*/
|
|
255
|
+
export function computeCoo(snapshot: GraphSnapshot): CooView {
|
|
256
|
+
return cooViewOf(snapshot, expandRowPtr(snapshot.rowPtr, snapshot.nodeCount, snapshot.arcCount));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Wrap a per-arc source array as the COO view of a snapshot (the shape `computeCoo()` returns; also
|
|
261
|
+
* used to install a carried `src` from the wire).
|
|
262
|
+
* @param snapshot - the snapshot
|
|
263
|
+
* @param src - source node of every arc, arcCount entries
|
|
264
|
+
* @returns the COO view
|
|
265
|
+
*/
|
|
266
|
+
export function cooViewOf(snapshot: GraphSnapshot, src: U32): CooView {
|
|
267
|
+
return Object.freeze({
|
|
268
|
+
src,
|
|
269
|
+
dst: snapshot.colIdx,
|
|
270
|
+
weights: snapshot.weights,
|
|
271
|
+
get arcToEdge(): U32 {
|
|
272
|
+
return snapshot.arcToEdge;
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Compute the edge list view (design section 7.2): every logical edge once in declared orientation.
|
|
279
|
+
* `src[e]` is the row holding `edgeToArc[e]`, `dst[e] === colIdx[edgeToArc[e]]`, `arc` aliases
|
|
280
|
+
* `edgeToArc` through a getter, `weights` is gathered through `edgeToArc` (aliased when the
|
|
281
|
+
* permutation is the identity, in which case `dst` aliases `colIdx` too).
|
|
282
|
+
* @param snapshot - the snapshot
|
|
283
|
+
* @returns the edge list view
|
|
284
|
+
*/
|
|
285
|
+
export function computeEdgeList(snapshot: GraphSnapshot): EdgeListView {
|
|
286
|
+
const { nodeCount, edgeCount, arcCount, rowPtr, colIdx, weights } = snapshot;
|
|
287
|
+
if (snapshot.flags.arcToEdgeIsIdentity) {
|
|
288
|
+
return edgeListViewOf(snapshot, expandRowPtr(rowPtr, nodeCount, arcCount), colIdx, weights);
|
|
289
|
+
}
|
|
290
|
+
const { arcToEdge, edgeToArc } = snapshot;
|
|
291
|
+
const src = new Uint32Array(edgeCount);
|
|
292
|
+
const dst = new Uint32Array(edgeCount);
|
|
293
|
+
for (let u = 0; u < nodeCount; u++) {
|
|
294
|
+
const end = rowPtr[u + 1];
|
|
295
|
+
for (let a = rowPtr[u]; a < end; a++) {
|
|
296
|
+
const e = arcToEdge[a];
|
|
297
|
+
if (edgeToArc[e] === a) {
|
|
298
|
+
src[e] = u;
|
|
299
|
+
dst[e] = colIdx[a];
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
let edgeWeights: F32 | null = null;
|
|
304
|
+
if (weights !== null) {
|
|
305
|
+
edgeWeights = new Float32Array(edgeCount);
|
|
306
|
+
for (let e = 0; e < edgeCount; e++) {
|
|
307
|
+
edgeWeights[e] = weights[edgeToArc[e]];
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return edgeListViewOf(snapshot, src, dst, edgeWeights);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Wrap per-edge arrays as the edge list view of a snapshot (the shape `computeEdgeList()` returns;
|
|
315
|
+
* also used to install carried arrays from the wire). `arc` reaches `edgeToArc` through a getter so
|
|
316
|
+
* an identity permutation is not materialised by building the view.
|
|
317
|
+
* @param snapshot - the snapshot
|
|
318
|
+
* @param src - declared source of every edge, edgeCount entries
|
|
319
|
+
* @param dst - declared target of every edge, edgeCount entries (colIdx itself when the permutation is the identity)
|
|
320
|
+
* @param weights - per-edge weights, or null when unweighted
|
|
321
|
+
* @returns the edge list view
|
|
322
|
+
*/
|
|
323
|
+
export function edgeListViewOf(snapshot: GraphSnapshot, src: U32, dst: U32, weights: F32 | null): EdgeListView {
|
|
324
|
+
return Object.freeze({
|
|
325
|
+
src,
|
|
326
|
+
dst,
|
|
327
|
+
weights,
|
|
328
|
+
get arc(): U32 {
|
|
329
|
+
return snapshot.edgeToArc;
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ============================================================ degrees
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Out-arc counts: `rowPtr[u + 1] - rowPtr[u]` (a self-loop counted once, design section 3.4).
|
|
338
|
+
* @param rowPtr - the row offsets
|
|
339
|
+
* @param nodeCount - the node count
|
|
340
|
+
* @returns a fresh Uint32Array(nodeCount)
|
|
341
|
+
*/
|
|
342
|
+
export function rowLengths(rowPtr: U32, nodeCount: number): U32 {
|
|
343
|
+
const out = new Uint32Array(nodeCount);
|
|
344
|
+
for (let u = 0; u < nodeCount; u++) {
|
|
345
|
+
out[u] = rowPtr[u + 1] - rowPtr[u];
|
|
346
|
+
}
|
|
347
|
+
return out;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* The graph-theoretic degree (design section 3.4): in + out when directed, out + self-loops when
|
|
352
|
+
* undirected (a loop counts twice, the NetworkX convention).
|
|
353
|
+
* @param outDegree - the out-degree view
|
|
354
|
+
* @param inOrLoops - inDegree() when directed, selfLoopsPerNode() when undirected
|
|
355
|
+
* @returns a fresh Uint32Array(n)
|
|
356
|
+
*/
|
|
357
|
+
export function sumDegrees(outDegree: U32, inOrLoops: U32): U32 {
|
|
358
|
+
const out = new Uint32Array(outDegree.length);
|
|
359
|
+
for (let u = 0; u < out.length; u++) {
|
|
360
|
+
out[u] = outDegree[u] + inOrLoops[u];
|
|
361
|
+
}
|
|
362
|
+
return out;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Row sums of an arc-aligned weight array as f64 (design section 7.2): a self-loop arc counted once;
|
|
367
|
+
* a row may sum to 0 because zero weights are legal.
|
|
368
|
+
* @param rowPtr - the row offsets of the adjacency being summed (forward or reverse)
|
|
369
|
+
* @param weights - the arc-aligned weights of that adjacency
|
|
370
|
+
* @param nodeCount - the node count
|
|
371
|
+
* @returns a fresh Float64Array(nodeCount)
|
|
372
|
+
*/
|
|
373
|
+
export function rowWeightSums(rowPtr: U32, weights: F32, nodeCount: number): F64 {
|
|
374
|
+
const out = new Float64Array(nodeCount);
|
|
375
|
+
for (let u = 0; u < nodeCount; u++) {
|
|
376
|
+
const end = rowPtr[u + 1];
|
|
377
|
+
let sum = 0;
|
|
378
|
+
for (let a = rowPtr[u]; a < end; a++) {
|
|
379
|
+
sum += weights[a];
|
|
380
|
+
}
|
|
381
|
+
out[u] = sum;
|
|
382
|
+
}
|
|
383
|
+
return out;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* A u32 count vector widened to f64 (the unweighted case of the weighted-degree views).
|
|
388
|
+
* @param counts - the counts
|
|
389
|
+
* @returns a fresh Float64Array of the same length
|
|
390
|
+
*/
|
|
391
|
+
export function widenToF64(counts: U32): F64 {
|
|
392
|
+
return new Float64Array(counts);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Element-wise sum of two f64 vectors of equal length.
|
|
397
|
+
* @param a - the first vector
|
|
398
|
+
* @param b - the second vector
|
|
399
|
+
* @returns a fresh Float64Array
|
|
400
|
+
*/
|
|
401
|
+
export function sumF64(a: F64, b: F64): F64 {
|
|
402
|
+
const out = new Float64Array(a.length);
|
|
403
|
+
for (let i = 0; i < out.length; i++) {
|
|
404
|
+
out[i] = a[i] + b[i];
|
|
405
|
+
}
|
|
406
|
+
return out;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* The sum of weights over logical edges, each undirected edge once (design section 7.2), accumulated
|
|
411
|
+
* in f64. `edgeCount` when unweighted.
|
|
412
|
+
* @param snapshot - the snapshot
|
|
413
|
+
* @returns the total weight
|
|
414
|
+
*/
|
|
415
|
+
export function computeTotalWeight(snapshot: GraphSnapshot): number {
|
|
416
|
+
const { weights, edgeCount } = snapshot;
|
|
417
|
+
if (weights === null) {
|
|
418
|
+
return edgeCount;
|
|
419
|
+
}
|
|
420
|
+
let sum = 0;
|
|
421
|
+
if (snapshot.flags.arcToEdgeIsIdentity) {
|
|
422
|
+
for (let a = 0; a < weights.length; a++) {
|
|
423
|
+
sum += weights[a];
|
|
424
|
+
}
|
|
425
|
+
return sum;
|
|
426
|
+
}
|
|
427
|
+
const { edgeToArc } = snapshot;
|
|
428
|
+
for (let e = 0; e < edgeCount; e++) {
|
|
429
|
+
sum += weights[edgeToArc[e]];
|
|
430
|
+
}
|
|
431
|
+
return sum;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// ============================================================ self-loops
|
|
435
|
+
|
|
436
|
+
/** The two self-loop views, computed together in one O(n log d) pass. */
|
|
437
|
+
interface SelfLoopViews {
|
|
438
|
+
/** Arcs a with colIdx[a] === row(a), ascending. */
|
|
439
|
+
readonly selfLoopArcs: U32;
|
|
440
|
+
/** Loop arcs per node. */
|
|
441
|
+
readonly selfLoopsPerNode: U32;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Find every self-loop arc by a binary search per row (design section 7.2).
|
|
446
|
+
* @param snapshot - the snapshot
|
|
447
|
+
* @returns the loop arcs (length selfLoopCount) and the per-node counts
|
|
448
|
+
*/
|
|
449
|
+
export function computeSelfLoops(snapshot: GraphSnapshot): SelfLoopViews {
|
|
450
|
+
const { nodeCount, rowPtr, colIdx, selfLoopCount } = snapshot;
|
|
451
|
+
const perNode = new Uint32Array(nodeCount);
|
|
452
|
+
const arcs = new Uint32Array(selfLoopCount);
|
|
453
|
+
let at = 0;
|
|
454
|
+
for (let u = 0; u < nodeCount; u++) {
|
|
455
|
+
const [lo, hi] = arcRangeIn(rowPtr, colIdx, u, u);
|
|
456
|
+
perNode[u] = hi - lo;
|
|
457
|
+
for (let a = lo; a < hi && at < selfLoopCount; a++) {
|
|
458
|
+
arcs[at++] = a;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return { selfLoopArcs: arcs, selfLoopsPerNode: perNode };
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Sum of weights over each node's self-loop arcs as f64; the loop counts widened when unweighted.
|
|
466
|
+
* @param snapshot - the snapshot
|
|
467
|
+
* @param loops - the self-loop views
|
|
468
|
+
* @returns a fresh Float64Array(n)
|
|
469
|
+
*/
|
|
470
|
+
export function computeSelfLoopWeight(snapshot: GraphSnapshot, loops: SelfLoopViews): F64 {
|
|
471
|
+
const { weights, nodeCount, rowPtr, colIdx } = snapshot;
|
|
472
|
+
if (weights === null) {
|
|
473
|
+
return widenToF64(loops.selfLoopsPerNode);
|
|
474
|
+
}
|
|
475
|
+
const out = new Float64Array(nodeCount);
|
|
476
|
+
for (let u = 0; u < nodeCount; u++) {
|
|
477
|
+
if (loops.selfLoopsPerNode[u] === 0) {
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
const [lo, hi] = arcRangeIn(rowPtr, colIdx, u, u);
|
|
481
|
+
let sum = 0;
|
|
482
|
+
for (let a = lo; a < hi; a++) {
|
|
483
|
+
sum += weights[a];
|
|
484
|
+
}
|
|
485
|
+
out[u] = sum;
|
|
486
|
+
}
|
|
487
|
+
return out;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// ============================================================ mate
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* The E_INVALID_SNAPSHOT error the mate walk raises when the doubled storage of invariant I7 does not
|
|
494
|
+
* hold (the row of the mate does not hold the expected source at the cursor).
|
|
495
|
+
* @param u - the row being walked
|
|
496
|
+
* @param v - the target whose mate group was expected
|
|
497
|
+
* @param arc - the arc where the walk failed
|
|
498
|
+
* @returns the error
|
|
499
|
+
*/
|
|
500
|
+
function pairingError(u: number, v: number, arc: number): GraphFormatError {
|
|
501
|
+
return new GraphFormatError(
|
|
502
|
+
"E_INVALID_SNAPSHOT",
|
|
503
|
+
`invariant I7 violated: arc ${arc} in row ${u} targeting ${v} has no mate in row ${v}`,
|
|
504
|
+
{ invariant: "I7", row: u, arc, target: v },
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* For every arc of an undirected snapshot, the arc storing the opposite orientation of the same edge
|
|
510
|
+
* (a self-loop maps to itself), by the O(m) lockstep walk of design section 6.4: rows are visited in
|
|
511
|
+
* ascending order, every group of k parallel arcs u -> v (v > u) is paired k-th to k-th with the group
|
|
512
|
+
* v -> u at row v's cursor, and the cursor advances so that by the time row v is visited every
|
|
513
|
+
* target below v has been consumed. Throws E_DIRECTED on a directed snapshot.
|
|
514
|
+
* @param snapshot - an undirected snapshot
|
|
515
|
+
* @returns a fresh Uint32Array(arcCount)
|
|
516
|
+
*/
|
|
517
|
+
export function computeMate(snapshot: GraphSnapshot): U32 {
|
|
518
|
+
if (snapshot.directed) {
|
|
519
|
+
throw new GraphFormatError("E_DIRECTED", "mate() is defined for undirected snapshots only", {
|
|
520
|
+
directed: true,
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
const { nodeCount, arcCount, rowPtr, colIdx } = snapshot;
|
|
524
|
+
const mate = new Uint32Array(arcCount);
|
|
525
|
+
const cursor = rowPtr.slice(0, nodeCount);
|
|
526
|
+
for (let u = 0; u < nodeCount; u++) {
|
|
527
|
+
const end = rowPtr[u + 1];
|
|
528
|
+
let a = cursor[u];
|
|
529
|
+
while (a < end) {
|
|
530
|
+
const v = colIdx[a];
|
|
531
|
+
let g = a + 1;
|
|
532
|
+
while (g < end && colIdx[g] === v) {
|
|
533
|
+
g++;
|
|
534
|
+
}
|
|
535
|
+
const k = g - a;
|
|
536
|
+
if (v === u) {
|
|
537
|
+
for (let i = 0; i < k; i++) {
|
|
538
|
+
mate[a + i] = a + i;
|
|
539
|
+
}
|
|
540
|
+
} else {
|
|
541
|
+
const b = cursor[v];
|
|
542
|
+
if (v < u || b + k > rowPtr[v + 1] || colIdx[b] !== u || colIdx[b + k - 1] !== u) {
|
|
543
|
+
throw pairingError(u, v, a);
|
|
544
|
+
}
|
|
545
|
+
for (let i = 0; i < k; i++) {
|
|
546
|
+
mate[a + i] = b + i;
|
|
547
|
+
mate[b + i] = a + i;
|
|
548
|
+
}
|
|
549
|
+
cursor[v] = b + k;
|
|
550
|
+
}
|
|
551
|
+
a = g;
|
|
552
|
+
}
|
|
553
|
+
cursor[u] = end;
|
|
554
|
+
}
|
|
555
|
+
return mate;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// ============================================================ degree order
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Nodes permuted by descending out-degree of an adjacency (counting sort, ties in ascending node
|
|
562
|
+
* index) with the cuGraph tier boundaries (design section 7.2): `segmentOffsets = [0, hiEnd,
|
|
563
|
+
* midEnd, lowEnd, n]` for the thresholds 1024 / 32 / 1.
|
|
564
|
+
* @param rowPtr - the row offsets of the adjacency to order by (forward, or reverse for the in-degree)
|
|
565
|
+
* @param nodeCount - the node count
|
|
566
|
+
* @returns the permutation and its tier offsets
|
|
567
|
+
*/
|
|
568
|
+
export function computeDegreeOrder(rowPtr: U32, nodeCount: number): DegreeOrderView {
|
|
569
|
+
const degree = rowLengths(rowPtr, nodeCount);
|
|
570
|
+
let maxDegree = 0;
|
|
571
|
+
for (let u = 0; u < nodeCount; u++) {
|
|
572
|
+
if (degree[u] > maxDegree) {
|
|
573
|
+
maxDegree = degree[u];
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
const start = new Uint32Array(maxDegree + 1);
|
|
577
|
+
for (let u = 0; u < nodeCount; u++) {
|
|
578
|
+
start[degree[u]]++;
|
|
579
|
+
}
|
|
580
|
+
// descending: the start of degree d is the number of nodes with a larger degree
|
|
581
|
+
let run = 0;
|
|
582
|
+
for (let d = maxDegree; d >= 0; d--) {
|
|
583
|
+
const count = start[d];
|
|
584
|
+
start[d] = run;
|
|
585
|
+
run += count;
|
|
586
|
+
}
|
|
587
|
+
const perm = new Uint32Array(nodeCount);
|
|
588
|
+
let hi = 0;
|
|
589
|
+
let mid = 0;
|
|
590
|
+
let low = 0;
|
|
591
|
+
for (let u = 0; u < nodeCount; u++) {
|
|
592
|
+
const d = degree[u];
|
|
593
|
+
perm[start[d]++] = u;
|
|
594
|
+
if (d >= DEGREE_TIER_HIGH) {
|
|
595
|
+
hi++;
|
|
596
|
+
} else if (d >= DEGREE_TIER_MID) {
|
|
597
|
+
mid++;
|
|
598
|
+
} else if (d >= DEGREE_TIER_LOW) {
|
|
599
|
+
low++;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
const segmentOffsets = new Uint32Array([0, hi, hi + mid, hi + mid + low, nodeCount]);
|
|
603
|
+
return Object.freeze({ perm, segmentOffsets });
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// ============================================================ symmetry
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Whether a directed snapshot's arc set is closed under reversal with equal weights (design sections
|
|
610
|
+
* 3.6 and 7.2): forward row v and reverse row v hold the same targets (versus sources; both sorted,
|
|
611
|
+
* so one lockstep walk), and within each run of parallel arcs to one target the same multiset of
|
|
612
|
+
* weights. Parallel arcs are ordered by edge index in both rows, so their weights can arrive in a
|
|
613
|
+
* different order (an expanded undirected multigraph with unequal parallel weights, design section
|
|
614
|
+
* 6.6); each run is compared as a sorted multiset, which is positional for simple graphs. Undirected
|
|
615
|
+
* snapshots are symmetric by construction.
|
|
616
|
+
* @param snapshot - the snapshot
|
|
617
|
+
* @param reverse - its reverse view
|
|
618
|
+
* @returns true when symmetric
|
|
619
|
+
*/
|
|
620
|
+
export function computeSymmetric(snapshot: GraphSnapshot, reverse: ReverseView): boolean {
|
|
621
|
+
if (!snapshot.directed) {
|
|
622
|
+
return true;
|
|
623
|
+
}
|
|
624
|
+
const { nodeCount, arcCount, rowPtr, colIdx, weights } = snapshot;
|
|
625
|
+
for (let v = 0; v <= nodeCount; v++) {
|
|
626
|
+
if (rowPtr[v] !== reverse.rowPtr[v]) {
|
|
627
|
+
return false;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
const revIdx = reverse.colIdx;
|
|
631
|
+
for (let a = 0; a < arcCount; a++) {
|
|
632
|
+
if (colIdx[a] !== revIdx[a]) {
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
const revWeights = reverse.weights;
|
|
637
|
+
if (weights === null || revWeights === null) {
|
|
638
|
+
return true;
|
|
639
|
+
}
|
|
640
|
+
if (!snapshot.flags.multigraph) {
|
|
641
|
+
for (let a = 0; a < arcCount; a++) {
|
|
642
|
+
if (weights[a] !== revWeights[a]) {
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
return true;
|
|
647
|
+
}
|
|
648
|
+
// multigraph: compare each parallel run (adjacent by I4, equal targets in both rows) as a multiset
|
|
649
|
+
let a = 0;
|
|
650
|
+
while (a < arcCount) {
|
|
651
|
+
let b = a + 1;
|
|
652
|
+
while (b < arcCount && colIdx[b] === colIdx[a] && b < rowPtr[rowOf(rowPtr, nodeCount, a) + 1]) {
|
|
653
|
+
b++;
|
|
654
|
+
}
|
|
655
|
+
if (b - a === 1) {
|
|
656
|
+
if (weights[a] !== revWeights[a]) {
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
} else if (!sameWeightMultiset(weights, revWeights, a, b)) {
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
a = b;
|
|
663
|
+
}
|
|
664
|
+
return true;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* The row holding arc `a` (binary search on rowPtr).
|
|
669
|
+
* @param rowPtr - the row offsets
|
|
670
|
+
* @param nodeCount - the node count
|
|
671
|
+
* @param a - the arc
|
|
672
|
+
* @returns the row
|
|
673
|
+
*/
|
|
674
|
+
function rowOf(rowPtr: U32, nodeCount: number, a: number): number {
|
|
675
|
+
let lo = 0;
|
|
676
|
+
let hi = nodeCount;
|
|
677
|
+
while (hi - lo > 1) {
|
|
678
|
+
const mid = (lo + hi) >>> 1;
|
|
679
|
+
if (rowPtr[mid] <= a) {
|
|
680
|
+
lo = mid;
|
|
681
|
+
} else {
|
|
682
|
+
hi = mid;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return lo;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Whether two weight runs hold the same multiset (sorted scratch copies compared position by position).
|
|
690
|
+
* @param x - one weight array
|
|
691
|
+
* @param y - the other
|
|
692
|
+
* @param start - the first arc of the run
|
|
693
|
+
* @param end - one past the last arc of the run
|
|
694
|
+
* @returns true when the multisets agree
|
|
695
|
+
*/
|
|
696
|
+
function sameWeightMultiset(x: F32, y: F32, start: number, end: number): boolean {
|
|
697
|
+
const p = x.slice(start, end).sort();
|
|
698
|
+
const q = y.slice(start, end).sort();
|
|
699
|
+
for (let i = 0; i < p.length; i++) {
|
|
700
|
+
if (p[i] !== q[i] && !(Number.isNaN(p[i]) && Number.isNaN(q[i]))) {
|
|
701
|
+
return false;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
return true;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// ============================================================ byte accounting
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* The bytes a cached view owns beyond the snapshot's core (aliased arrays count zero), for
|
|
711
|
+
* `byteLength({ views: true })` (design section 7.2).
|
|
712
|
+
* @param snapshot - the snapshot
|
|
713
|
+
* @param name - the view name
|
|
714
|
+
* @param value - the cached value
|
|
715
|
+
* @returns the byte count
|
|
716
|
+
*/
|
|
717
|
+
export function viewByteLength(snapshot: GraphSnapshot, name: ViewName, value: unknown): number {
|
|
718
|
+
switch (name) {
|
|
719
|
+
case "reverse":
|
|
720
|
+
return value instanceof ReverseAdjacency ? value.ownedByteLength : 0;
|
|
721
|
+
case "coo": {
|
|
722
|
+
const coo = value as CooView;
|
|
723
|
+
return coo.src.byteLength;
|
|
724
|
+
}
|
|
725
|
+
case "edgeList": {
|
|
726
|
+
const list = value as EdgeListView;
|
|
727
|
+
let bytes = list.src.byteLength;
|
|
728
|
+
if (list.dst !== snapshot.colIdx) {
|
|
729
|
+
bytes += list.dst.byteLength;
|
|
730
|
+
}
|
|
731
|
+
if (list.weights !== null && list.weights !== snapshot.weights) {
|
|
732
|
+
bytes += list.weights.byteLength;
|
|
733
|
+
}
|
|
734
|
+
return bytes;
|
|
735
|
+
}
|
|
736
|
+
case "outDegree":
|
|
737
|
+
case "inDegree":
|
|
738
|
+
case "degree":
|
|
739
|
+
case "weightedOutDegree":
|
|
740
|
+
case "weightedInDegree":
|
|
741
|
+
case "weightedDegree":
|
|
742
|
+
case "selfLoopWeight":
|
|
743
|
+
case "selfLoopArcs":
|
|
744
|
+
case "selfLoopsPerNode":
|
|
745
|
+
case "mate":
|
|
746
|
+
return (value as ArrayBufferView).byteLength;
|
|
747
|
+
case "degreeOrder":
|
|
748
|
+
case "reverseDegreeOrder": {
|
|
749
|
+
const order = value as DegreeOrderView;
|
|
750
|
+
return order.perm.byteLength + order.segmentOffsets.byteLength;
|
|
751
|
+
}
|
|
752
|
+
case "totalWeight":
|
|
753
|
+
case "symmetric":
|
|
754
|
+
return 0;
|
|
755
|
+
default: {
|
|
756
|
+
const unknown: never = name;
|
|
757
|
+
throw new GraphFormatError("E_UNSUPPORTED", `unknown view ${String(unknown)}`, { view: unknown });
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* The typed arrays a cached view currently holds, keyed by member name, for the checksum records of
|
|
764
|
+
* design section 5.8 (aliased core arrays are included: a write through the alias is a violation
|
|
765
|
+
* too). Scalar views hold nothing.
|
|
766
|
+
* @param snapshot - the snapshot
|
|
767
|
+
* @param name - the view name
|
|
768
|
+
* @param value - the cached value
|
|
769
|
+
* @returns the arrays by member name
|
|
770
|
+
*/
|
|
771
|
+
export function viewArrays(
|
|
772
|
+
snapshot: GraphSnapshot,
|
|
773
|
+
name: ViewName,
|
|
774
|
+
value: unknown,
|
|
775
|
+
): Readonly<Record<string, ArrayBufferView>> {
|
|
776
|
+
switch (name) {
|
|
777
|
+
case "reverse":
|
|
778
|
+
return value instanceof ReverseAdjacency ? value.materialised() : {};
|
|
779
|
+
case "coo": {
|
|
780
|
+
const coo = value as CooView;
|
|
781
|
+
return { src: coo.src };
|
|
782
|
+
}
|
|
783
|
+
case "edgeList": {
|
|
784
|
+
const list = value as EdgeListView;
|
|
785
|
+
const out: Record<string, ArrayBufferView> = { src: list.src };
|
|
786
|
+
if (list.dst !== snapshot.colIdx) {
|
|
787
|
+
out.dst = list.dst;
|
|
788
|
+
}
|
|
789
|
+
if (list.weights !== null && list.weights !== snapshot.weights) {
|
|
790
|
+
out.weights = list.weights;
|
|
791
|
+
}
|
|
792
|
+
return out;
|
|
793
|
+
}
|
|
794
|
+
case "outDegree":
|
|
795
|
+
case "inDegree":
|
|
796
|
+
case "degree":
|
|
797
|
+
case "weightedOutDegree":
|
|
798
|
+
case "weightedInDegree":
|
|
799
|
+
case "weightedDegree":
|
|
800
|
+
case "selfLoopWeight":
|
|
801
|
+
case "selfLoopArcs":
|
|
802
|
+
case "selfLoopsPerNode":
|
|
803
|
+
case "mate":
|
|
804
|
+
return { data: value as ArrayBufferView };
|
|
805
|
+
case "degreeOrder":
|
|
806
|
+
case "reverseDegreeOrder": {
|
|
807
|
+
const order = value as DegreeOrderView;
|
|
808
|
+
return { perm: order.perm, segmentOffsets: order.segmentOffsets };
|
|
809
|
+
}
|
|
810
|
+
case "totalWeight":
|
|
811
|
+
case "symmetric":
|
|
812
|
+
return {};
|
|
813
|
+
default: {
|
|
814
|
+
const unknown: never = name;
|
|
815
|
+
throw new GraphFormatError("E_UNSUPPORTED", `unknown view ${String(unknown)}`, { view: unknown });
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// ============================================================ boundary helpers (7.5)
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* A zeroed vector of the same class as `like`.
|
|
824
|
+
* @param like - the vector whose class to match
|
|
825
|
+
* @param length - the element count
|
|
826
|
+
* @returns the new vector
|
|
827
|
+
*/
|
|
828
|
+
function allocVector<T extends NumericVector>(like: T, length: number): T {
|
|
829
|
+
if (like instanceof Float64Array) {
|
|
830
|
+
return new Float64Array(length) as T;
|
|
831
|
+
}
|
|
832
|
+
if (like instanceof Float32Array) {
|
|
833
|
+
return new Float32Array(length) as T;
|
|
834
|
+
}
|
|
835
|
+
if (like instanceof Int32Array) {
|
|
836
|
+
return new Int32Array(length) as T;
|
|
837
|
+
}
|
|
838
|
+
return new Uint32Array(length) as T;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* Reduce a per-arc vector (arcCount entries) to a per-edge vector (edgeCount entries) through
|
|
843
|
+
* `arcToEdge` (design section 7.5): `"first"` takes the value of the arc holding the declared
|
|
844
|
+
* orientation (`edgeToArc[e]`), `"sum"` / `"max"` / `"min"` combine both arcs of an undirected edge.
|
|
845
|
+
* Returns `perArc` itself when the permutation is the identity and no `out` is given.
|
|
846
|
+
* @param snapshot - the snapshot the vector is aligned to
|
|
847
|
+
* @param perArc - the per-arc values, arcCount entries
|
|
848
|
+
* @param reducer - how the arcs of one edge combine
|
|
849
|
+
* @param out - an optional destination of edgeCount entries
|
|
850
|
+
* @returns the per-edge vector
|
|
851
|
+
*/
|
|
852
|
+
export function foldArcs<T extends NumericVector>(
|
|
853
|
+
snapshot: GraphSnapshot,
|
|
854
|
+
perArc: T,
|
|
855
|
+
reducer: "first" | "sum" | "max" | "min",
|
|
856
|
+
out?: T,
|
|
857
|
+
): T {
|
|
858
|
+
const { edgeCount, arcCount } = snapshot;
|
|
859
|
+
assertOneOf("reducer", reducer, ["first", "sum", "max", "min"] as const);
|
|
860
|
+
if (perArc.length !== arcCount) {
|
|
861
|
+
throw new GraphFormatError("E_COLUMN_LENGTH", `perArc has ${perArc.length} entries, expected ${arcCount}`, {
|
|
862
|
+
expected: arcCount,
|
|
863
|
+
found: perArc.length,
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
if (snapshot.flags.arcToEdgeIsIdentity) {
|
|
867
|
+
if (out === undefined) {
|
|
868
|
+
return perArc;
|
|
869
|
+
}
|
|
870
|
+
out.set(perArc);
|
|
871
|
+
return out;
|
|
872
|
+
}
|
|
873
|
+
const result = out ?? allocVector(perArc, edgeCount);
|
|
874
|
+
const { edgeToArc, arcToEdge } = snapshot;
|
|
875
|
+
for (let e = 0; e < edgeCount; e++) {
|
|
876
|
+
result[e] = perArc[edgeToArc[e]];
|
|
877
|
+
}
|
|
878
|
+
switch (reducer) {
|
|
879
|
+
case "first":
|
|
880
|
+
break;
|
|
881
|
+
case "sum":
|
|
882
|
+
for (let a = 0; a < arcCount; a++) {
|
|
883
|
+
const e = arcToEdge[a];
|
|
884
|
+
if (edgeToArc[e] !== a) {
|
|
885
|
+
result[e] += perArc[a];
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
break;
|
|
889
|
+
case "max":
|
|
890
|
+
for (let a = 0; a < arcCount; a++) {
|
|
891
|
+
const e = arcToEdge[a];
|
|
892
|
+
if (perArc[a] > result[e]) {
|
|
893
|
+
result[e] = perArc[a];
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
break;
|
|
897
|
+
case "min":
|
|
898
|
+
for (let a = 0; a < arcCount; a++) {
|
|
899
|
+
const e = arcToEdge[a];
|
|
900
|
+
if (perArc[a] < result[e]) {
|
|
901
|
+
result[e] = perArc[a];
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
break;
|
|
905
|
+
default: {
|
|
906
|
+
const unknown: never = reducer;
|
|
907
|
+
throw new GraphFormatError("E_UNSUPPORTED", `unknown reducer ${String(unknown)}`, { reducer: unknown });
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
return result;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
/**
|
|
914
|
+
* Expand a per-edge vector (edgeCount entries) to a per-arc vector (arcCount entries) through
|
|
915
|
+
* `arcToEdge` (design section 7.5). Returns `perEdge` itself when the permutation is the identity and
|
|
916
|
+
* no `out` is given.
|
|
917
|
+
* @param snapshot - the snapshot the vector is aligned to
|
|
918
|
+
* @param perEdge - the per-edge values, edgeCount entries
|
|
919
|
+
* @param out - an optional destination of arcCount entries
|
|
920
|
+
* @returns the per-arc vector
|
|
921
|
+
*/
|
|
922
|
+
export function expandEdges<T extends NumericVector>(snapshot: GraphSnapshot, perEdge: T, out?: T): T {
|
|
923
|
+
const { edgeCount, arcCount } = snapshot;
|
|
924
|
+
if (perEdge.length !== edgeCount) {
|
|
925
|
+
throw new GraphFormatError("E_COLUMN_LENGTH", `perEdge has ${perEdge.length} entries, expected ${edgeCount}`, {
|
|
926
|
+
expected: edgeCount,
|
|
927
|
+
found: perEdge.length,
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
if (snapshot.flags.arcToEdgeIsIdentity) {
|
|
931
|
+
if (out === undefined) {
|
|
932
|
+
return perEdge;
|
|
933
|
+
}
|
|
934
|
+
out.set(perEdge);
|
|
935
|
+
return out;
|
|
936
|
+
}
|
|
937
|
+
const result = out ?? allocVector(perEdge, arcCount);
|
|
938
|
+
const { arcToEdge } = snapshot;
|
|
939
|
+
for (let a = 0; a < arcCount; a++) {
|
|
940
|
+
result[a] = perEdge[arcToEdge[a]];
|
|
941
|
+
}
|
|
942
|
+
return result;
|
|
943
|
+
}
|