@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.
Files changed (224) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +216 -43
  3. package/dist/graph-format.d.ts +1 -0
  4. package/dist/graph-format.d.ts.map +1 -0
  5. package/dist/graph-format.js +15798 -0
  6. package/dist/graph-format.js.map +1 -0
  7. package/dist/src/builder/arena.d.ts +85 -0
  8. package/dist/src/builder/arena.d.ts.map +1 -0
  9. package/dist/src/builder/arena.js +154 -0
  10. package/dist/src/builder/arena.js.map +1 -0
  11. package/dist/src/builder/compact.d.ts +515 -0
  12. package/dist/src/builder/compact.d.ts.map +1 -0
  13. package/dist/src/builder/compact.js +1553 -0
  14. package/dist/src/builder/compact.js.map +1 -0
  15. package/dist/src/builder/counting-sort.d.ts +96 -0
  16. package/dist/src/builder/counting-sort.d.ts.map +1 -0
  17. package/dist/src/builder/counting-sort.js +430 -0
  18. package/dist/src/builder/counting-sort.js.map +1 -0
  19. package/dist/src/builder/freeze.d.ts +72 -0
  20. package/dist/src/builder/freeze.d.ts.map +1 -0
  21. package/dist/src/builder/freeze.js +548 -0
  22. package/dist/src/builder/freeze.js.map +1 -0
  23. package/dist/src/builder/graph-builder.d.ts +602 -0
  24. package/dist/src/builder/graph-builder.d.ts.map +1 -0
  25. package/dist/src/builder/graph-builder.js +1940 -0
  26. package/dist/src/builder/graph-builder.js.map +1 -0
  27. package/dist/src/builder/options.d.ts +16 -0
  28. package/dist/src/builder/options.d.ts.map +1 -0
  29. package/dist/src/builder/options.js +21 -0
  30. package/dist/src/builder/options.js.map +1 -0
  31. package/dist/src/columns/bitmap.d.ts +96 -0
  32. package/dist/src/columns/bitmap.d.ts.map +1 -0
  33. package/dist/src/columns/bitmap.js +184 -0
  34. package/dist/src/columns/bitmap.js.map +1 -0
  35. package/dist/src/columns/column.d.ts +220 -0
  36. package/dist/src/columns/column.d.ts.map +1 -0
  37. package/dist/src/columns/column.js +2109 -0
  38. package/dist/src/columns/column.js.map +1 -0
  39. package/dist/src/columns/dictionary.d.ts +60 -0
  40. package/dist/src/columns/dictionary.d.ts.map +1 -0
  41. package/dist/src/columns/dictionary.js +102 -0
  42. package/dist/src/columns/dictionary.js.map +1 -0
  43. package/dist/src/columns/growable.d.ts +198 -0
  44. package/dist/src/columns/growable.d.ts.map +1 -0
  45. package/dist/src/columns/growable.js +296 -0
  46. package/dist/src/columns/growable.js.map +1 -0
  47. package/dist/src/columns/infer.d.ts +121 -0
  48. package/dist/src/columns/infer.d.ts.map +1 -0
  49. package/dist/src/columns/infer.js +268 -0
  50. package/dist/src/columns/infer.js.map +1 -0
  51. package/dist/src/columns/remap.d.ts +143 -0
  52. package/dist/src/columns/remap.d.ts.map +1 -0
  53. package/dist/src/columns/remap.js +588 -0
  54. package/dist/src/columns/remap.js.map +1 -0
  55. package/dist/src/columns/table.d.ts +184 -0
  56. package/dist/src/columns/table.d.ts.map +1 -0
  57. package/dist/src/columns/table.js +485 -0
  58. package/dist/src/columns/table.js.map +1 -0
  59. package/dist/src/constants.d.ts +34 -0
  60. package/dist/src/constants.d.ts.map +1 -0
  61. package/dist/src/constants.js +79 -0
  62. package/dist/src/constants.js.map +1 -0
  63. package/dist/src/errors.d.ts +76 -0
  64. package/dist/src/errors.d.ts.map +1 -0
  65. package/dist/src/errors.js +32 -0
  66. package/dist/src/errors.js.map +1 -0
  67. package/dist/src/ids/edge-id-index.d.ts +77 -0
  68. package/dist/src/ids/edge-id-index.d.ts.map +1 -0
  69. package/dist/src/ids/edge-id-index.js +155 -0
  70. package/dist/src/ids/edge-id-index.js.map +1 -0
  71. package/dist/src/ids/node-id-map.d.ts +280 -0
  72. package/dist/src/ids/node-id-map.d.ts.map +1 -0
  73. package/dist/src/ids/node-id-map.js +1139 -0
  74. package/dist/src/ids/node-id-map.js.map +1 -0
  75. package/dist/src/ids/string-store.d.ts +219 -0
  76. package/dist/src/ids/string-store.d.ts.map +1 -0
  77. package/dist/src/ids/string-store.js +526 -0
  78. package/dist/src/ids/string-store.js.map +1 -0
  79. package/dist/src/index.d.ts +30 -0
  80. package/dist/src/index.d.ts.map +1 -0
  81. package/dist/src/index.js +29 -0
  82. package/dist/src/index.js.map +1 -0
  83. package/dist/src/populate/from-csr.d.ts +61 -0
  84. package/dist/src/populate/from-csr.d.ts.map +1 -0
  85. package/dist/src/populate/from-csr.js +706 -0
  86. package/dist/src/populate/from-csr.js.map +1 -0
  87. package/dist/src/populate/from-edge-arrays.d.ts +51 -0
  88. package/dist/src/populate/from-edge-arrays.d.ts.map +1 -0
  89. package/dist/src/populate/from-edge-arrays.js +217 -0
  90. package/dist/src/populate/from-edge-arrays.js.map +1 -0
  91. package/dist/src/populate/from-records.d.ts +51 -0
  92. package/dist/src/populate/from-records.d.ts.map +1 -0
  93. package/dist/src/populate/from-records.js +447 -0
  94. package/dist/src/populate/from-records.js.map +1 -0
  95. package/dist/src/snapshot/derived.d.ts +211 -0
  96. package/dist/src/snapshot/derived.d.ts.map +1 -0
  97. package/dist/src/snapshot/derived.js +1254 -0
  98. package/dist/src/snapshot/derived.js.map +1 -0
  99. package/dist/src/snapshot/graph-meta.d.ts +23 -0
  100. package/dist/src/snapshot/graph-meta.d.ts.map +1 -0
  101. package/dist/src/snapshot/graph-meta.js +159 -0
  102. package/dist/src/snapshot/graph-meta.js.map +1 -0
  103. package/dist/src/snapshot/graph-snapshot.d.ts +509 -0
  104. package/dist/src/snapshot/graph-snapshot.d.ts.map +1 -0
  105. package/dist/src/snapshot/graph-snapshot.js +1247 -0
  106. package/dist/src/snapshot/graph-snapshot.js.map +1 -0
  107. package/dist/src/snapshot/hash.d.ts +90 -0
  108. package/dist/src/snapshot/hash.d.ts.map +1 -0
  109. package/dist/src/snapshot/hash.js +228 -0
  110. package/dist/src/snapshot/hash.js.map +1 -0
  111. package/dist/src/snapshot/queries.d.ts +74 -0
  112. package/dist/src/snapshot/queries.d.ts.map +1 -0
  113. package/dist/src/snapshot/queries.js +131 -0
  114. package/dist/src/snapshot/queries.js.map +1 -0
  115. package/dist/src/snapshot/validate.d.ts +192 -0
  116. package/dist/src/snapshot/validate.d.ts.map +1 -0
  117. package/dist/src/snapshot/validate.js +990 -0
  118. package/dist/src/snapshot/validate.js.map +1 -0
  119. package/dist/src/snapshot/views.d.ts +274 -0
  120. package/dist/src/snapshot/views.d.ts.map +1 -0
  121. package/dist/src/snapshot/views.js +856 -0
  122. package/dist/src/snapshot/views.js.map +1 -0
  123. package/dist/src/types/builder.d.ts +456 -0
  124. package/dist/src/types/builder.d.ts.map +1 -0
  125. package/dist/src/types/builder.js +11 -0
  126. package/dist/src/types/builder.js.map +1 -0
  127. package/dist/src/types/columns.d.ts +615 -0
  128. package/dist/src/types/columns.d.ts.map +1 -0
  129. package/dist/src/types/columns.js +14 -0
  130. package/dist/src/types/columns.js.map +1 -0
  131. package/dist/src/types/index.d.ts +14 -0
  132. package/dist/src/types/index.d.ts.map +1 -0
  133. package/dist/src/types/index.js +11 -0
  134. package/dist/src/types/index.js.map +1 -0
  135. package/dist/src/types/internal.d.ts +172 -0
  136. package/dist/src/types/internal.d.ts.map +1 -0
  137. package/dist/src/types/internal.js +13 -0
  138. package/dist/src/types/internal.js.map +1 -0
  139. package/dist/src/types/snapshot.d.ts +801 -0
  140. package/dist/src/types/snapshot.d.ts.map +1 -0
  141. package/dist/src/types/snapshot.js +14 -0
  142. package/dist/src/types/snapshot.js.map +1 -0
  143. package/dist/src/types/wire.d.ts +196 -0
  144. package/dist/src/types/wire.d.ts.map +1 -0
  145. package/dist/src/types/wire.js +8 -0
  146. package/dist/src/types/wire.js.map +1 -0
  147. package/dist/src/util/mask.d.ts +55 -0
  148. package/dist/src/util/mask.d.ts.map +1 -0
  149. package/dist/src/util/mask.js +71 -0
  150. package/dist/src/util/mask.js.map +1 -0
  151. package/dist/src/util/options.d.ts +22 -0
  152. package/dist/src/util/options.d.ts.map +1 -0
  153. package/dist/src/util/options.js +49 -0
  154. package/dist/src/util/options.js.map +1 -0
  155. package/dist/src/util/shared-buffers.d.ts +42 -0
  156. package/dist/src/util/shared-buffers.d.ts.map +1 -0
  157. package/dist/src/util/shared-buffers.js +52 -0
  158. package/dist/src/util/shared-buffers.js.map +1 -0
  159. package/dist/src/util/typed-array.d.ts +205 -0
  160. package/dist/src/util/typed-array.d.ts.map +1 -0
  161. package/dist/src/util/typed-array.js +289 -0
  162. package/dist/src/util/typed-array.js.map +1 -0
  163. package/dist/src/wire/bytes.d.ts +69 -0
  164. package/dist/src/wire/bytes.d.ts.map +1 -0
  165. package/dist/src/wire/bytes.js +361 -0
  166. package/dist/src/wire/bytes.js.map +1 -0
  167. package/dist/src/wire/carried-views.d.ts +111 -0
  168. package/dist/src/wire/carried-views.d.ts.map +1 -0
  169. package/dist/src/wire/carried-views.js +286 -0
  170. package/dist/src/wire/carried-views.js.map +1 -0
  171. package/dist/src/wire/from-wire.d.ts +144 -0
  172. package/dist/src/wire/from-wire.d.ts.map +1 -0
  173. package/dist/src/wire/from-wire.js +1425 -0
  174. package/dist/src/wire/from-wire.js.map +1 -0
  175. package/dist/src/wire/to-wire.d.ts +171 -0
  176. package/dist/src/wire/to-wire.d.ts.map +1 -0
  177. package/dist/src/wire/to-wire.js +652 -0
  178. package/dist/src/wire/to-wire.js.map +1 -0
  179. package/dist/tsconfig.build.tsbuildinfo +1 -0
  180. package/package.json +72 -7
  181. package/src/builder/arena.ts +192 -0
  182. package/src/builder/compact.ts +1811 -0
  183. package/src/builder/counting-sort.ts +506 -0
  184. package/src/builder/freeze.ts +689 -0
  185. package/src/builder/graph-builder.ts +2264 -0
  186. package/src/builder/options.ts +29 -0
  187. package/src/columns/bitmap.ts +196 -0
  188. package/src/columns/column.ts +2574 -0
  189. package/src/columns/dictionary.ts +114 -0
  190. package/src/columns/growable.ts +350 -0
  191. package/src/columns/infer.ts +285 -0
  192. package/src/columns/remap.ts +663 -0
  193. package/src/columns/table.ts +568 -0
  194. package/src/constants.ts +89 -0
  195. package/src/errors.ts +115 -0
  196. package/src/ids/edge-id-index.ts +179 -0
  197. package/src/ids/node-id-map.ts +1340 -0
  198. package/src/ids/string-store.ts +589 -0
  199. package/src/index.ts +139 -0
  200. package/src/lib-resizable-array-buffer.d.ts +34 -0
  201. package/src/populate/from-csr.ts +802 -0
  202. package/src/populate/from-edge-arrays.ts +257 -0
  203. package/src/populate/from-records.ts +536 -0
  204. package/src/snapshot/derived.ts +1486 -0
  205. package/src/snapshot/graph-meta.ts +167 -0
  206. package/src/snapshot/graph-snapshot.ts +1503 -0
  207. package/src/snapshot/hash.ts +241 -0
  208. package/src/snapshot/queries.ts +136 -0
  209. package/src/snapshot/validate.ts +1108 -0
  210. package/src/snapshot/views.ts +943 -0
  211. package/src/types/builder.ts +480 -0
  212. package/src/types/columns.ts +727 -0
  213. package/src/types/index.ts +14 -0
  214. package/src/types/internal.ts +195 -0
  215. package/src/types/snapshot.ts +886 -0
  216. package/src/types/wire.ts +207 -0
  217. package/src/util/mask.ts +89 -0
  218. package/src/util/options.ts +57 -0
  219. package/src/util/shared-buffers.ts +56 -0
  220. package/src/util/typed-array.ts +371 -0
  221. package/src/wire/bytes.ts +440 -0
  222. package/src/wire/carried-views.ts +327 -0
  223. package/src/wire/from-wire.ts +1690 -0
  224. 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
+ }