@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,706 @@
1
+ /**
2
+ * `fromCsr()` (design section 8.1, entry point 2; decision C12): adopt prebuilt CSR arrays -- from
3
+ * a file, a worker, another package or a generator that emits CSR directly -- without copying by
4
+ * default (GraphBLAS pack semantics: the caller transfers ownership and must not mutate them
5
+ * afterwards), with full validation by default because adopted arrays typically come from a file or
6
+ * the network (design section 9.5).
7
+ *
8
+ * The pipeline: resolve the optional arrays (an absent `arcToEdge` is the identity of a directed
9
+ * graph, an absent `edgeToArc` is derived in one O(m) pass, `edgeCount` defaults to the arc count or
10
+ * is derived from `arcToEdge`); adopt or copy; detect an already-aligned shared buffer as the arena
11
+ * (design section 10.3); build a candidate snapshot; check the rows (invariant I4) when `sortRows`
12
+ * is set and, when some row is unsorted, rebuild a fresh sorted core through the freeze pipeline's
13
+ * counting sorts over the declared edge list (the caller's arrays are not modified; the cost is a
14
+ * freeze); validate at the requested level; verify the caller's flag claims (invariant I9).
15
+ *
16
+ * `detectArena()` is exported for `fromWire`, which applies the same detection when a manifest
17
+ * carries no arena descriptor (design section 8.1).
18
+ */
19
+ import { copyCoreIntoArena, CORE_ORDER } from "../builder/arena.js";
20
+ import { sortIntoCore } from "../builder/counting-sort.js";
21
+ import { createTable, tableWithColumns } from "../columns/table.js";
22
+ import { ALIGNMENT, INVALID_INDEX, MAX_COUNT } from "../constants.js";
23
+ import { GraphFormatError } from "../errors.js";
24
+ import { identityNodeIdMap, nodeIdMapFromF64, nodeIdMapFromIds } from "../ids/node-id-map.js";
25
+ import { EMPTY_GRAPH_META, resolveGraphMeta as resolveSharedGraphMeta } from "../snapshot/graph-meta.js";
26
+ import { createSnapshot } from "../snapshot/graph-snapshot.js";
27
+ import { checkI5Orientation, checkI6, checkI8NaN, checkI11Bijection, checkUniqueColumn, computeFlags, countLoopArcs, invariantViolation, isIdentity, validateFull, validateStructure, } from "../snapshot/validate.js";
28
+ import { isOverPlainBuffer } from "../util/typed-array.js";
29
+ // ============================================================ constants
30
+ /** The flag names, for claim verification. */
31
+ const FLAG_NAMES = [
32
+ "multigraph",
33
+ "hasSelfLoops",
34
+ "arcToEdgeIsIdentity",
35
+ "weighted",
36
+ "allWeightsOne",
37
+ "nonNegativeWeights",
38
+ "finiteWeights",
39
+ ];
40
+ const VALIDATION_LEVELS = new Set(["none", "structure", "full"]);
41
+ /**
42
+ * The E_INVALID_SNAPSHOT error for an input array of the wrong class.
43
+ * @param name - the array
44
+ * @param expected - the class name expected
45
+ * @returns the error
46
+ */
47
+ function arrayTypeError(name, expected) {
48
+ return new GraphFormatError("E_INVALID_SNAPSHOT", `${name} must be a ${expected}`, {
49
+ array: name,
50
+ expected,
51
+ reason: "dtype",
52
+ });
53
+ }
54
+ /**
55
+ * Check the classes of the input arrays (a Float64Array passed as weights or a plain array passed as
56
+ * colIdx would otherwise produce a snapshot whose bytes are wrong on the GPU) and the node count.
57
+ * @param input - the input
58
+ */
59
+ function checkShape(input) {
60
+ if (!Number.isInteger(input.nodeCount) || input.nodeCount < 0 || input.nodeCount > MAX_COUNT) {
61
+ throw new GraphFormatError("E_TOO_LARGE", `nodeCount ${input.nodeCount} is not an integer in [0, MAX_COUNT]`, {
62
+ count: input.nodeCount,
63
+ max: MAX_COUNT,
64
+ });
65
+ }
66
+ if (!(input.rowPtr instanceof Uint32Array)) {
67
+ throw arrayTypeError("rowPtr", "Uint32Array");
68
+ }
69
+ if (!(input.colIdx instanceof Uint32Array)) {
70
+ throw arrayTypeError("colIdx", "Uint32Array");
71
+ }
72
+ if (input.weights !== undefined && input.weights !== null && !(input.weights instanceof Float32Array)) {
73
+ throw arrayTypeError("weights", "Float32Array");
74
+ }
75
+ if (input.arcToEdge !== undefined && !(input.arcToEdge instanceof Uint32Array)) {
76
+ throw arrayTypeError("arcToEdge", "Uint32Array");
77
+ }
78
+ if (input.edgeToArc !== undefined && !(input.edgeToArc instanceof Uint32Array)) {
79
+ throw arrayTypeError("edgeToArc", "Uint32Array");
80
+ }
81
+ if (input.edgeCount !== undefined && (!Number.isInteger(input.edgeCount) || input.edgeCount < 0)) {
82
+ throw invariantViolation("I3", `edgeCount ${input.edgeCount} is not a non-negative integer`, {
83
+ count: "edgeCount",
84
+ found: input.edgeCount,
85
+ });
86
+ }
87
+ if (input.rowPtr.length !== input.nodeCount + 1) {
88
+ throw invariantViolation("I1", `rowPtr has ${input.rowPtr.length} entries, expected ${input.nodeCount + 1}`, {
89
+ expected: input.nodeCount + 1,
90
+ found: input.rowPtr.length,
91
+ });
92
+ }
93
+ }
94
+ /**
95
+ * Whether a typed array is backed by a plain, fixed-length ArrayBuffer (a SharedArrayBuffer is never
96
+ * adopted, decision D-SAB; a resizable buffer's view can change length, invariants I10 and I17).
97
+ * @param array - the array
98
+ * @returns true for a plain ArrayBuffer
99
+ */
100
+ function overPlainBuffer(array) {
101
+ return isOverPlainBuffer(array);
102
+ }
103
+ /**
104
+ * Derive `edgeToArc` from `arcToEdge`: the first (lowest) arc holding each edge, which for a directed
105
+ * graph is the edge's only arc and for an undirected graph the arc in the lower-numbered row
106
+ * (design section 8.1: "derived in one O(m) pass"). An edge no arc holds is left INVALID_INDEX so the
107
+ * I5 range check names it.
108
+ * @param arcToEdge - the arc -> edge map
109
+ * @param edgeCount - the edge count
110
+ * @returns the edge -> arc map
111
+ */
112
+ function deriveEdgeToArc(arcToEdge, edgeCount) {
113
+ const edgeToArc = new Uint32Array(edgeCount).fill(INVALID_INDEX);
114
+ for (let a = 0; a < arcToEdge.length; a++) {
115
+ const e = arcToEdge[a];
116
+ if (e < edgeCount && edgeToArc[e] === INVALID_INDEX) {
117
+ edgeToArc[e] = a;
118
+ }
119
+ }
120
+ return edgeToArc;
121
+ }
122
+ /**
123
+ * The E_INVALID_SNAPSHOT error for an identity-flag contradiction between the input arrays.
124
+ * @param message - what is wrong
125
+ * @param details - the location
126
+ * @returns the error
127
+ */
128
+ function identityError(message, details) {
129
+ return invariantViolation("I5", message, details);
130
+ }
131
+ /**
132
+ * Resolve the optional core arrays and counts (design section 8.1). For a directed input an absent
133
+ * `arcToEdge` is the identity and a supplied one that happens to be the identity is dropped (the
134
+ * snapshot materialises identity permutations lazily, design section 3.1); an undirected input must
135
+ * supply `arcToEdge` (E_INVALID_SNAPSHOT, invariant I5). `edgeCount` defaults to the arc count
136
+ * (directed) or to max(arcToEdge) + 1 (undirected). `selfLoopCount` is the number of loop arcs.
137
+ * @param input - the input
138
+ * @param level - the validation level ("none" skips the edgeToArc identity check)
139
+ * @returns the resolved core
140
+ */
141
+ function resolveCore(input, level) {
142
+ const { directed, nodeCount, rowPtr, colIdx } = input;
143
+ const arcCount = colIdx.length;
144
+ const weights = input.weights ?? null;
145
+ const selfLoopCount = countLoopArcs(rowPtr, colIdx, nodeCount);
146
+ let arcToEdge = input.arcToEdge ?? null;
147
+ let edgeToArc = input.edgeToArc ?? null;
148
+ let edgeCount;
149
+ if (directed) {
150
+ edgeCount = input.edgeCount ?? arcCount;
151
+ if (edgeCount !== arcCount) {
152
+ throw invariantViolation("I6", `directed input has arcCount ${arcCount} but edgeCount ${edgeCount}`, {
153
+ arcCount,
154
+ edgeCount,
155
+ });
156
+ }
157
+ // decided from the supplied array at every level (O(m)): a flag claim never drops an array
158
+ const identity = arcToEdge === null || (arcToEdge.length === arcCount && isIdentity(arcToEdge));
159
+ if (identity) {
160
+ if (edgeToArc !== null && level !== "none" && (edgeToArc.length !== edgeCount || !isIdentity(edgeToArc))) {
161
+ throw identityError("arcToEdge is the identity but edgeToArc is not", {
162
+ array: "edgeToArc",
163
+ found: edgeToArc.length,
164
+ expected: edgeCount,
165
+ });
166
+ }
167
+ arcToEdge = null;
168
+ edgeToArc = null;
169
+ }
170
+ else if (edgeToArc === null) {
171
+ edgeToArc = deriveEdgeToArc(arcToEdge, edgeCount);
172
+ }
173
+ }
174
+ else {
175
+ if (arcToEdge === null) {
176
+ throw identityError("an undirected input must supply arcToEdge", {
177
+ array: "arcToEdge",
178
+ reason: "missing arcToEdge",
179
+ });
180
+ }
181
+ const { edgeCount: given } = input;
182
+ if (given === undefined) {
183
+ let max = -1;
184
+ for (let a = 0; a < arcToEdge.length; a++) {
185
+ if (arcToEdge[a] > max) {
186
+ max = arcToEdge[a];
187
+ }
188
+ }
189
+ edgeCount = max + 1;
190
+ }
191
+ else {
192
+ edgeCount = given;
193
+ }
194
+ edgeToArc ?? (edgeToArc = deriveEdgeToArc(arcToEdge, edgeCount));
195
+ }
196
+ return { rowPtr, colIdx, weights, arcToEdge, edgeToArc, edgeCount, arcCount, selfLoopCount };
197
+ }
198
+ // ============================================================ adoption and the arena
199
+ /**
200
+ * The core arrays of a resolved core in the shape the arena helpers take.
201
+ * @param core - the resolved core
202
+ * @param arena - the arena, or null
203
+ * @returns the core arrays
204
+ */
205
+ function coreArraysOf(core, arena) {
206
+ return {
207
+ rowPtr: core.rowPtr,
208
+ colIdx: core.colIdx,
209
+ weights: core.weights,
210
+ arcToEdge: core.arcToEdge,
211
+ edgeToArc: core.edgeToArc,
212
+ arena,
213
+ };
214
+ }
215
+ /**
216
+ * Detect whether adopted core arrays already form an arena (design sections 8.1 and 10.3): every
217
+ * present, non-empty, non-identity core array is a view over ONE plain ArrayBuffer, laid out hot to
218
+ * cold (rowPtr, colIdx, weights, arcToEdge, edgeToArc) without overlap, each starting at a multiple
219
+ * of 256 bytes relative to `rowPtr`. When they do, that buffer is the arena: `byteOffset` is
220
+ * rowPtr's, `byteLength` spans to the end of the last array and `hotByteLength` ends at the weights
221
+ * (or colIdx, or rowPtr) segment. Otherwise null. Arrays whose segment would be null (a zero-length
222
+ * array, an absent weights array, an identity permutation given as null) do not take part.
223
+ * @param core - the core arrays; `arcToEdge` / `edgeToArc` null when identity
224
+ * @returns the arena layout, or null when the arrays are not an aligned shared buffer
225
+ */
226
+ export function detectArena(core) {
227
+ const { buffer } = core.rowPtr;
228
+ if (!(buffer instanceof ArrayBuffer)) {
229
+ return null;
230
+ }
231
+ const base = core.rowPtr.byteOffset;
232
+ const arrays = [
233
+ core.rowPtr,
234
+ core.colIdx,
235
+ core.weights,
236
+ core.arcToEdge,
237
+ core.edgeToArc,
238
+ ];
239
+ const segments = {
240
+ rowPtr: null,
241
+ colIdx: null,
242
+ weights: null,
243
+ arcToEdge: null,
244
+ edgeToArc: null,
245
+ };
246
+ let cursor = base;
247
+ let hotEnd = base;
248
+ for (let i = 0; i < CORE_ORDER.length; i++) {
249
+ const array = arrays[i];
250
+ if (array === null || array.byteLength === 0) {
251
+ continue;
252
+ }
253
+ if (array.buffer !== buffer || array.byteOffset < cursor || (array.byteOffset - base) % ALIGNMENT !== 0) {
254
+ return null;
255
+ }
256
+ segments[CORE_ORDER[i]] = Object.freeze({ byteOffset: array.byteOffset, byteLength: array.byteLength });
257
+ cursor = array.byteOffset + array.byteLength;
258
+ if (i <= 2) {
259
+ hotEnd = cursor;
260
+ }
261
+ }
262
+ return Object.freeze({
263
+ buffer,
264
+ byteOffset: base,
265
+ byteLength: cursor - base,
266
+ alignment: ALIGNMENT,
267
+ segments: Object.freeze(segments),
268
+ hotByteLength: hotEnd - base,
269
+ });
270
+ }
271
+ /**
272
+ * Whether any present core array is not over a plain ArrayBuffer, which forces the copy path.
273
+ * @param core - the resolved core
274
+ * @returns true when a copy is required
275
+ */
276
+ function needsCopy(core) {
277
+ const arrays = [core.rowPtr, core.colIdx, core.weights, core.arcToEdge, core.edgeToArc];
278
+ return arrays.some((array) => array !== null && !overPlainBuffer(array));
279
+ }
280
+ // ============================================================ ids, columns, meta
281
+ /**
282
+ * The id map of a CsrInput (design section 4.2): identity with no storage when no ids are given;
283
+ * otherwise the kind detected from the ids, validated (E_INVALID_ID / E_DUPLICATE_ID) unless the
284
+ * level is "none". A Float64Array is adopted by reference unless `copy` is set.
285
+ * @param input - the input
286
+ * @param level - the validation level
287
+ * @param copy - whether to copy an F64 array
288
+ * @returns the id map; E_COLUMN_LENGTH when ids.length !== nodeCount
289
+ */
290
+ function idMapOf(input, level, copy) {
291
+ const { ids, nodeCount } = input;
292
+ if (ids === undefined) {
293
+ return identityNodeIdMap(nodeCount);
294
+ }
295
+ if (ids.length !== nodeCount) {
296
+ throw new GraphFormatError("E_COLUMN_LENGTH", `ids has ${ids.length} entries, expected nodeCount ${nodeCount}`, {
297
+ field: "ids",
298
+ expected: nodeCount,
299
+ found: ids.length,
300
+ });
301
+ }
302
+ const validate = level !== "none";
303
+ if (ids instanceof Float64Array) {
304
+ const values = copy || !overPlainBuffer(ids) ? new Float64Array(ids) : ids;
305
+ return nodeIdMapFromF64(values, { validate });
306
+ }
307
+ return nodeIdMapFromIds(copy ? [...ids] : ids, nodeCount, { validate });
308
+ }
309
+ /**
310
+ * The node or edge table of a CsrInput: the caller's columns attached through the same path as
311
+ * `withColumns()` (typed arrays adopted by reference, JS arrays resolved with inference; design
312
+ * section 5.7), copied first when `copy` is set.
313
+ * @param domain - "node" or "edge"
314
+ * @param rowCount - the table's row count
315
+ * @param columns - the caller's columns, or undefined
316
+ * @param copy - whether to copy typed arrays
317
+ * @returns the table
318
+ */
319
+ function tableOf(domain, rowCount, columns, copy) {
320
+ const table = createTable(domain, rowCount);
321
+ if (columns === undefined) {
322
+ return table;
323
+ }
324
+ // a typed array over a SharedArrayBuffer or a resizable buffer is copied like the core (D-SAB)
325
+ const copied = {};
326
+ for (const name of Object.keys(columns)) {
327
+ const value = columns[name];
328
+ if (ArrayBuffer.isView(value)) {
329
+ copied[name] = copy || !overPlainBuffer(value) ? value.slice() : value;
330
+ }
331
+ else if (ArrayBuffer.isView(value.data)) {
332
+ copied[name] =
333
+ copy || !overPlainBuffer(value.data) ? { data: value.data.slice(), decl: value.decl } : value;
334
+ }
335
+ else {
336
+ copied[name] = value;
337
+ }
338
+ }
339
+ return tableWithColumns(table, copied);
340
+ }
341
+ /**
342
+ * Resolve a GraphMetaPatch into a complete GraphMeta (design section 5.9) with the builder's
343
+ * `setMeta` rules over empty metadata (E_COLUMN_TYPE with details.field on a bad field).
344
+ * @param patch - the patch, or undefined for no metadata
345
+ * @returns the metadata, frozen
346
+ */
347
+ export function resolveGraphMeta(patch) {
348
+ return patch === undefined ? EMPTY_GRAPH_META : resolveSharedGraphMeta(EMPTY_GRAPH_META, patch);
349
+ }
350
+ // ============================================================ flags
351
+ /**
352
+ * Whether every flag is claimed, so no pass over the arrays is needed to know them.
353
+ * @param claims - the caller's claims
354
+ * @returns true when all seven flags are defined
355
+ */
356
+ function fullyClaimed(claims) {
357
+ return claims !== undefined && FLAG_NAMES.every((name) => claims[name] !== undefined);
358
+ }
359
+ /**
360
+ * The truthful flags of a core (design section 3.8) computed from the arrays.
361
+ * @param directed - the direction
362
+ * @param nodeCount - the node count
363
+ * @param core - the core arrays
364
+ * @param selfLoopCount - the loop count
365
+ * @returns the flags
366
+ */
367
+ function flagsOf(directed, nodeCount, core, selfLoopCount) {
368
+ return computeFlags({
369
+ directed,
370
+ nodeCount,
371
+ rowPtr: core.rowPtr,
372
+ colIdx: core.colIdx,
373
+ weights: core.weights,
374
+ arcToEdge: core.arcToEdge,
375
+ selfLoopCount,
376
+ });
377
+ }
378
+ /**
379
+ * Verify the caller's flag claims against the computed flags (invariant I9): every claimed flag must
380
+ * equal the truth.
381
+ * @param claims - the claims, or undefined
382
+ * @param flags - the truthful flags
383
+ */
384
+ function verifyClaims(claims, flags) {
385
+ if (claims === undefined) {
386
+ return;
387
+ }
388
+ for (const name of FLAG_NAMES) {
389
+ const claim = claims[name];
390
+ if (claim !== undefined && claim !== flags[name]) {
391
+ throw invariantViolation("I9", `flag ${name} is claimed ${claim} but the arrays say ${flags[name]}`, {
392
+ flag: name,
393
+ found: claim,
394
+ expected: flags[name],
395
+ });
396
+ }
397
+ }
398
+ }
399
+ // ============================================================ rows and the rebuild
400
+ /**
401
+ * Whether every row satisfies invariant I4: colIdx non-decreasing within the row, and parallel arcs in
402
+ * ascending arcToEdge order.
403
+ * @param s - the candidate snapshot
404
+ * @returns true when sorted
405
+ */
406
+ function rowsSorted(s) {
407
+ const { rowPtr, colIdx, nodeCount } = s;
408
+ const arcToEdge = s.flags.arcToEdgeIsIdentity ? null : s.arcToEdge;
409
+ for (let u = 0; u < nodeCount; u++) {
410
+ const end = rowPtr[u + 1];
411
+ for (let a = rowPtr[u] + 1; a < end; a++) {
412
+ if (colIdx[a] < colIdx[a - 1]) {
413
+ return false;
414
+ }
415
+ if (colIdx[a] === colIdx[a - 1] && arcToEdge !== null && arcToEdge[a] <= arcToEdge[a - 1]) {
416
+ return false;
417
+ }
418
+ }
419
+ }
420
+ return true;
421
+ }
422
+ /**
423
+ * The declared edge list of a candidate whose rows are unsorted: for every logical edge the arc
424
+ * `edgeToArc[e]` gives the declared source (its row), target and weight.
425
+ * @param s - the candidate snapshot
426
+ * @returns the per-edge arrays
427
+ */
428
+ function declaredEdges(s) {
429
+ const { nodeCount, edgeCount, rowPtr, colIdx, weights } = s;
430
+ const identity = s.flags.arcToEdgeIsIdentity;
431
+ const src = new Uint32Array(edgeCount);
432
+ const dst = new Uint32Array(edgeCount);
433
+ const edgeWeights = weights === null ? null : new Float32Array(edgeCount);
434
+ const arcToEdge = identity ? null : s.arcToEdge;
435
+ const edgeToArc = identity ? null : s.edgeToArc;
436
+ for (let u = 0; u < nodeCount; u++) {
437
+ const end = rowPtr[u + 1];
438
+ for (let a = rowPtr[u]; a < end; a++) {
439
+ const e = arcToEdge === null ? a : arcToEdge[a];
440
+ if (edgeToArc !== null && edgeToArc[e] !== a) {
441
+ continue;
442
+ }
443
+ src[e] = u;
444
+ dst[e] = colIdx[a];
445
+ if (edgeWeights !== null && weights !== null) {
446
+ edgeWeights[e] = weights[a];
447
+ }
448
+ }
449
+ }
450
+ return { src, dst, weights: edgeWeights };
451
+ }
452
+ /**
453
+ * Compare the rebuilt sorted core with the unsorted input row by row as multisets of (target, edge,
454
+ * weight) (the "full" level's pairing and weight checks of invariant I7 for an input that could not
455
+ * be walked in sorted order): an arc without a mate, a mate on a different edge, or unequal mate
456
+ * weights all leave the rebuilt rows different from the input rows.
457
+ * @param input - the candidate over the caller's arrays
458
+ * @param rebuilt - the rebuilt snapshot
459
+ */
460
+ function compareRows(input, rebuilt) {
461
+ const { nodeCount } = input;
462
+ for (let u = 0; u <= nodeCount; u++) {
463
+ if (input.rowPtr[u] !== rebuilt.rowPtr[u]) {
464
+ throw invariantViolation("I7", `row ${u - 1} holds arcs whose mates are missing from their rows`, {
465
+ row: Math.max(0, u - 1),
466
+ found: input.rowPtr[u],
467
+ expected: rebuilt.rowPtr[u],
468
+ });
469
+ }
470
+ }
471
+ const inArcToEdge = input.arcToEdge;
472
+ const outArcToEdge = rebuilt.arcToEdge;
473
+ const inWeights = input.weights;
474
+ const outWeights = rebuilt.weights;
475
+ for (let u = 0; u < nodeCount; u++) {
476
+ const start = input.rowPtr[u];
477
+ const end = input.rowPtr[u + 1];
478
+ const order = new Uint32Array(end - start);
479
+ for (let i = 0; i < order.length; i++) {
480
+ order[i] = start + i;
481
+ }
482
+ order.sort((x, y) => input.colIdx[x] - input.colIdx[y] || inArcToEdge[x] - inArcToEdge[y]);
483
+ for (let i = 0; i < order.length; i++) {
484
+ const a = order[i];
485
+ const b = start + i;
486
+ const sameArc = input.colIdx[a] === rebuilt.colIdx[b] && inArcToEdge[a] === outArcToEdge[b];
487
+ const sameWeight = inWeights === null || outWeights === null || inWeights[a] === outWeights[b];
488
+ if (!sameArc || !sameWeight) {
489
+ throw invariantViolation("I7", `arc ${a} in row ${u} has no matching mate`, {
490
+ row: u,
491
+ arc: a,
492
+ target: input.colIdx[a],
493
+ edge: inArcToEdge[a],
494
+ });
495
+ }
496
+ }
497
+ }
498
+ }
499
+ /**
500
+ * Rebuild a sorted core from a candidate whose rows are unsorted through the freeze pipeline's
501
+ * counting sorts (design section 6.3 steps 3-6 and 8) over the declared edge list, into a fresh arena
502
+ * as `freeze()` would. Logical edge indices are preserved; the caller's arrays are not modified.
503
+ * @param candidate - the candidate over the caller's arrays
504
+ * @param parts - the candidate's parts (ids, tables, meta are reused)
505
+ * @returns the rebuilt parts, with the truthful flags of the sorted core
506
+ */
507
+ function rebuildSorted(candidate, parts) {
508
+ const edges = declaredEdges(candidate);
509
+ const sorted = sortIntoCore({
510
+ directed: candidate.directed,
511
+ nodeCount: candidate.nodeCount,
512
+ edgeCount: candidate.edgeCount,
513
+ src: edges.src,
514
+ dst: edges.dst,
515
+ weights: edges.weights,
516
+ }, true);
517
+ return {
518
+ ...parts,
519
+ arcCount: sorted.arcCount,
520
+ selfLoopCount: sorted.selfLoopCount,
521
+ rowPtr: sorted.core.rowPtr,
522
+ colIdx: sorted.core.colIdx,
523
+ weights: sorted.core.weights,
524
+ arcToEdge: sorted.core.arcToEdge,
525
+ edgeToArc: sorted.core.edgeToArc,
526
+ flags: sorted.flags,
527
+ arena: sorted.core.arena,
528
+ };
529
+ }
530
+ /**
531
+ * The order-independent checks of the "full" level over a candidate that is about to be rebuilt:
532
+ * the permutation rules (I5 orientation, I6), NaN weights (I8), the id bijection (I11) and unique
533
+ * columns. Sortedness (I4), pairing (I7) and the flags (I9) are established by the rebuild itself.
534
+ * @param s - the candidate
535
+ */
536
+ function orderIndependentFullChecks(s) {
537
+ checkI5Orientation(s);
538
+ checkI6(s, true);
539
+ checkI8NaN(s);
540
+ checkI11Bijection(s);
541
+ const tables = [
542
+ ["nodes", s.nodes],
543
+ ["edges", s.edges],
544
+ ["graph", s.graph],
545
+ ];
546
+ for (const [name, table] of tables) {
547
+ for (const column of table) {
548
+ checkUniqueColumn(name, column);
549
+ }
550
+ }
551
+ }
552
+ // ============================================================ the entry point
553
+ /**
554
+ * Resolve the options with their defaults, checking the validation level.
555
+ * @param options - the caller's options
556
+ * @returns the resolved values
557
+ */
558
+ function resolveOptions(options) {
559
+ const level = options.validate ?? "full";
560
+ if (!VALIDATION_LEVELS.has(level)) {
561
+ throw new GraphFormatError("E_UNSUPPORTED", `unsupported validation level ${level}`, {
562
+ field: "validate",
563
+ found: level,
564
+ reason: "unsupported option",
565
+ });
566
+ }
567
+ return { level, copy: options.copy === true, sortRows: options.sortRows !== false };
568
+ }
569
+ /**
570
+ * Build a snapshot from prebuilt CSR arrays (design section 8.1). The arrays are ADOPTED without
571
+ * copying by default (`copy: false`): the caller transfers ownership and must not mutate them
572
+ * afterwards; when they already share one plain ArrayBuffer at 256-byte-aligned offsets in arena
573
+ * order that buffer becomes the snapshot's arena, otherwise `arena` is null. With `copy: true` (or
574
+ * when an array is over a SharedArrayBuffer) the core is copied into a fresh arena. `validate`
575
+ * defaults to "full" (design section 9.5), which computes the flags and verifies the caller's claims
576
+ * (invariant I9); "structure" and "none" trust every claimed flag and compute only the unclaimed ones
577
+ * (design section 8.1: "structure" skips the sortedness / multigraph / flag checks and trusts
578
+ * `flags`). With `sortRows: true` (the default) invariant I4 is checked in O(m) and unsorted rows
579
+ * are rebuilt through the freeze pipeline into a fresh arena; `sortRows: false` asserts sorted rows,
580
+ * checked under "full". A directed input without `arcToEdge` (or with the identity) keeps identity
581
+ * permutations lazy, so `edgeList().weights` aliases `weights`.
582
+ * @param input - the arrays and counts; an undirected input must supply arcToEdge
583
+ * @param options - validation level, copy and sortRows
584
+ * @returns the snapshot; E_INVALID_SNAPSHOT (details.invariant, location) on every rejected input,
585
+ * E_INVALID_ID / E_DUPLICATE_ID for bad ids, E_COLUMN_LENGTH for a column or id array of the wrong
586
+ * length, E_TOO_LARGE for a node count above MAX_COUNT
587
+ */
588
+ export function fromCsr(input, options = {}) {
589
+ const { level, copy, sortRows } = resolveOptions(options);
590
+ checkShape(input);
591
+ const resolved = resolveCore(input, level);
592
+ const { directed, nodeCount } = input;
593
+ const claims = input.flags;
594
+ // adopt (detecting an arena) or copy
595
+ let core;
596
+ if (copy || needsCopy(resolved)) {
597
+ core = copyCoreIntoArena(coreArraysOf(resolved, null));
598
+ }
599
+ else {
600
+ core = coreArraysOf(resolved, detectArena(resolved));
601
+ }
602
+ // flags (design sections 3.8, 8.1 and 9.5): "full" computes them and verifies the claims (I9);
603
+ // "structure" and "none" trust every claimed flag and compute only the unclaimed ones
604
+ const trustClaims = level !== "full";
605
+ let flags;
606
+ if (trustClaims && fullyClaimed(claims)) {
607
+ flags = applyClaims({
608
+ ...claims,
609
+ weighted: core.weights !== null,
610
+ arcToEdgeIsIdentity: directed && core.arcToEdge === null,
611
+ }, claims);
612
+ }
613
+ else {
614
+ const computed = flagsOf(directed, nodeCount, core, resolved.selfLoopCount);
615
+ flags = trustClaims ? applyClaims(computed, claims) : computed;
616
+ }
617
+ const parts = {
618
+ label: null,
619
+ serial: null,
620
+ directed,
621
+ nodeCount,
622
+ edgeCount: resolved.edgeCount,
623
+ arcCount: resolved.arcCount,
624
+ selfLoopCount: resolved.selfLoopCount,
625
+ rowPtr: core.rowPtr,
626
+ colIdx: core.colIdx,
627
+ weights: core.weights,
628
+ arcToEdge: core.arcToEdge,
629
+ edgeToArc: core.edgeToArc,
630
+ flags,
631
+ ids: idMapOf(input, level, copy),
632
+ nodes: tableOf("node", nodeCount, input.nodeColumns, copy),
633
+ edges: tableOf("edge", resolved.edgeCount, input.edgeColumns, copy),
634
+ graph: createTable("graph", 1),
635
+ extensions: new Map(),
636
+ meta: resolveGraphMeta(input.meta),
637
+ arena: core.arena,
638
+ checksum: false,
639
+ };
640
+ const candidate = createSnapshot(parts);
641
+ if (sortRows && !rowsSorted(candidate)) {
642
+ // unsorted rows: check what can be checked without sorted rows, then rebuild
643
+ if (level !== "none") {
644
+ validateStructure(candidate);
645
+ }
646
+ if (level === "full") {
647
+ orderIndependentFullChecks(candidate);
648
+ }
649
+ const rebuiltParts = rebuildSorted(candidate, parts);
650
+ const rebuilt = createSnapshot({
651
+ ...rebuiltParts,
652
+ flags: trustClaims ? applyClaims(rebuiltParts.flags, claims) : rebuiltParts.flags,
653
+ });
654
+ if (level === "full") {
655
+ if (!directed) {
656
+ compareRows(candidate, rebuilt);
657
+ }
658
+ verifyClaims(claims, rebuilt.flags);
659
+ }
660
+ return rebuilt;
661
+ }
662
+ if (level === "structure") {
663
+ validateStructure(candidate);
664
+ }
665
+ else if (level === "full") {
666
+ validateFull(candidate);
667
+ verifyClaims(claims, candidate.flags);
668
+ }
669
+ return candidate;
670
+ }
671
+ /** The flags that describe which arrays are PRESENT; they are never taken from a claim (I9 at every level). */
672
+ const PRESENCE_FLAGS = ["weighted", "arcToEdgeIsIdentity"];
673
+ /**
674
+ * The trusted flags of a "structure" or "none" level construction: every claimed predicate flag as
675
+ * given, every other one as computed. The two presence flags (`weighted`, `arcToEdgeIsIdentity`)
676
+ * follow the arrays at every level, and a claim that contradicts them is I9 immediately: a claim
677
+ * must never change which arrays the snapshot holds.
678
+ * @param computed - the flags computed from the arrays
679
+ * @param claims - the caller's claims, or undefined
680
+ * @returns the flags to store
681
+ */
682
+ function applyClaims(computed, claims) {
683
+ if (claims === undefined) {
684
+ return computed;
685
+ }
686
+ const out = { ...computed };
687
+ for (const name of FLAG_NAMES) {
688
+ const value = claims[name];
689
+ if (value === undefined) {
690
+ continue;
691
+ }
692
+ if (PRESENCE_FLAGS.includes(name)) {
693
+ if (value !== computed[name]) {
694
+ throw invariantViolation("I9", `flag ${name} is claimed ${value} but the arrays say ${computed[name]}`, {
695
+ flag: name,
696
+ found: value,
697
+ expected: computed[name],
698
+ });
699
+ }
700
+ continue;
701
+ }
702
+ out[name] = value;
703
+ }
704
+ return out;
705
+ }
706
+ //# sourceMappingURL=from-csr.js.map