@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,689 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The freeze pipeline of design section 6.3, exactly: the pre-checks that throw without touching the
|
|
3
|
+
* builder (self-loop "error", arc-count bound), compaction of tombstones and dropped loops (steps 1-2,
|
|
4
|
+
* into a fresh staging the builder commits only on success), the two counting-sort passes into the
|
|
5
|
+
* arena (steps 3-6, counting-sort.ts), the duplicate-policy walk with its one repeat after a merge
|
|
6
|
+
* (step 7), the weight flags and the f64 / explicit-weight shadow column (step 8), the id map (step
|
|
7
|
+
* 9), the attribute tables (step 10), the arena and prepared views (step 11) and the snapshot itself
|
|
8
|
+
* with its FreezeReport (step 12).
|
|
9
|
+
*
|
|
10
|
+
* Nothing here mutates the builder: `runFreeze` returns the staging the builder must adopt (the
|
|
11
|
+
* source staging when nothing was renumbered, a compacted one otherwise) so that a throw anywhere in
|
|
12
|
+
* the pipeline leaves the builder as it was (design section 11.1).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { bitmapClear, bitmapGet, bitmapSet, bitmapWordCount } from "../columns/bitmap.js";
|
|
16
|
+
import { columnFromValues, createColumn, emptyParts, resolveColumnMeta } from "../columns/column.js";
|
|
17
|
+
import { AttributeTable, verifyUniqueColumns } from "../columns/table.js";
|
|
18
|
+
import { INVALID_INDEX, MAX_COUNT } from "../constants.js";
|
|
19
|
+
import { GraphFormatError } from "../errors.js";
|
|
20
|
+
import { identityNodeIdMap, nodeIdMapFromIds } from "../ids/node-id-map.js";
|
|
21
|
+
import { createSnapshot, type GraphSnapshot } from "../snapshot/graph-snapshot.js";
|
|
22
|
+
import { isIdentity } from "../snapshot/validate.js";
|
|
23
|
+
import {
|
|
24
|
+
type Column,
|
|
25
|
+
type ColumnDeclPatch,
|
|
26
|
+
type ColumnDomain,
|
|
27
|
+
type Dtype,
|
|
28
|
+
type DuplicatePolicy,
|
|
29
|
+
type F32,
|
|
30
|
+
type F64,
|
|
31
|
+
type FreezeOptions,
|
|
32
|
+
type FreezeReport,
|
|
33
|
+
type GraphMeta,
|
|
34
|
+
type NodeIdMap,
|
|
35
|
+
type ResolvedBuilderOptions,
|
|
36
|
+
type U32,
|
|
37
|
+
} from "../types/index.js";
|
|
38
|
+
import { type SnapshotParts } from "../types/internal.js";
|
|
39
|
+
import { copyCoreIntoArena } from "./arena.js";
|
|
40
|
+
import {
|
|
41
|
+
compactStaging,
|
|
42
|
+
type IndexRemap,
|
|
43
|
+
remapKeepingBits,
|
|
44
|
+
remapSurvivors,
|
|
45
|
+
type Staging,
|
|
46
|
+
type StagingColumn,
|
|
47
|
+
} from "./compact.js";
|
|
48
|
+
import { type SortInput, sortIntoCore, type SortResult } from "./counting-sort.js";
|
|
49
|
+
import { assertDuplicatePolicy } from "./options.js";
|
|
50
|
+
|
|
51
|
+
// ============================================================ context and outcome
|
|
52
|
+
|
|
53
|
+
/** One graph-level attribute the builder holds until freeze (design section 5.9). */
|
|
54
|
+
export interface GraphValue {
|
|
55
|
+
/** The declaration patch given to setGraphValue, or undefined. */
|
|
56
|
+
readonly decl: ColumnDeclPatch | undefined;
|
|
57
|
+
/** The value; undefined = unset. */
|
|
58
|
+
readonly value: unknown;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** A widening recorded by the builder since the previous freeze (design section 5.1). */
|
|
62
|
+
export interface Widening {
|
|
63
|
+
/** The column name. */
|
|
64
|
+
readonly column: string;
|
|
65
|
+
/** The column's table. */
|
|
66
|
+
readonly domain: ColumnDomain;
|
|
67
|
+
/** The dtype before. */
|
|
68
|
+
readonly from: Dtype;
|
|
69
|
+
/** The dtype after. */
|
|
70
|
+
readonly to: Dtype;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Everything the pipeline reads from the builder. */
|
|
74
|
+
export interface FreezeContext {
|
|
75
|
+
/** The staging to freeze; never modified. */
|
|
76
|
+
readonly staging: Staging;
|
|
77
|
+
/** The builder's current direction. */
|
|
78
|
+
readonly directed: boolean;
|
|
79
|
+
/** The resolved builder options (policies, precision). */
|
|
80
|
+
readonly options: ResolvedBuilderOptions;
|
|
81
|
+
/** The graph-level attributes in declaration order. */
|
|
82
|
+
readonly graphValues: ReadonlyMap<string, GraphValue>;
|
|
83
|
+
/** The graph metadata. */
|
|
84
|
+
readonly meta: GraphMeta;
|
|
85
|
+
/** The widenings since the previous freeze. */
|
|
86
|
+
readonly widened: readonly Widening[];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** What a freeze produces: the snapshot, its report, and the staging the builder must adopt. */
|
|
90
|
+
interface FreezeOutcome {
|
|
91
|
+
/** The frozen snapshot. */
|
|
92
|
+
readonly snapshot: GraphSnapshot;
|
|
93
|
+
/** The report. */
|
|
94
|
+
readonly report: FreezeReport;
|
|
95
|
+
/** The staging the builder holds afterwards: the source staging, or the compacted one. */
|
|
96
|
+
readonly staging: Staging;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The name of the shadow weight column the freeze writes (design sections 3.7 and 5.6). */
|
|
100
|
+
export const WEIGHT_COLUMN_NAME = "graphty.weight";
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* A millisecond clock for the profile timings.
|
|
104
|
+
* @returns the current time in milliseconds
|
|
105
|
+
*/
|
|
106
|
+
function now(): number {
|
|
107
|
+
return performance.now();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** A per-edge scratch: which survivor an edge folds into, and the reduced weights. */
|
|
111
|
+
interface DuplicateWalk {
|
|
112
|
+
/** old edge -> its survivor (itself for a survivor). */
|
|
113
|
+
readonly survivorOf: U32;
|
|
114
|
+
/** Per old edge: the reduced weight to store on a survivor, meaningful where `stored` is set; null when none. */
|
|
115
|
+
readonly reduced: Float64Array | null;
|
|
116
|
+
/** Per old edge: bit set on a survivor whose group produced a reduced weight to store; null when none. */
|
|
117
|
+
readonly stored: U32 | null;
|
|
118
|
+
/** Per old edge: bit set on a survivor whose weight is explicit after the merge although it was not before; null when none. */
|
|
119
|
+
readonly explicit: U32 | null;
|
|
120
|
+
/** How many edges folded into a survivor. */
|
|
121
|
+
readonly merged: number;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ============================================================ steps
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The E_SELF_LOOP pre-check of design section 6.3 step 2 (policy "error"): the first live loop is
|
|
128
|
+
* named. Runs before anything is renumbered so the builder is untouched.
|
|
129
|
+
* @param staging - the source staging
|
|
130
|
+
*/
|
|
131
|
+
function checkSelfLoops(staging: Staging): void {
|
|
132
|
+
if (staging.selfLoopCount === 0) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const bound = staging.edgeBound;
|
|
136
|
+
for (let e = 0; e < bound; e++) {
|
|
137
|
+
if (staging.edgeAlive.get(e) && staging.src.get(e) === staging.dst.get(e)) {
|
|
138
|
+
const node = staging.src.get(e);
|
|
139
|
+
throw new GraphFormatError("E_SELF_LOOP", `edge ${e} is a self-loop at node ${node}`, { edge: e, node });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* The live edges that are not self-loops, as a bitmap over the edge index space (the edges a
|
|
146
|
+
* `selfLoops: "drop"` freeze keeps, design section 6.3 step 2).
|
|
147
|
+
* @param staging - the source staging
|
|
148
|
+
* @returns a fresh bitmap: bit e set = edge e is live and src !== dst
|
|
149
|
+
*/
|
|
150
|
+
function liveNonLoopBits(staging: Staging): U32 {
|
|
151
|
+
const keep = staging.edgeAlive.view().slice();
|
|
152
|
+
const src = staging.src.view();
|
|
153
|
+
const dst = staging.dst.view();
|
|
154
|
+
const bound = staging.edgeBound;
|
|
155
|
+
for (let e = 0; e < bound; e++) {
|
|
156
|
+
if (src[e] === dst[e] && bitmapGet(keep, e)) {
|
|
157
|
+
bitmapClear(keep, e);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return keep;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The sort input over a staging's live (compacted) edges.
|
|
165
|
+
* @param staging - a staging without tombstones
|
|
166
|
+
* @param directed - the direction
|
|
167
|
+
* @returns the per-edge views
|
|
168
|
+
*/
|
|
169
|
+
function sortInputOf(staging: Staging, directed: boolean): SortInput {
|
|
170
|
+
const edgeCount = staging.edgeBound;
|
|
171
|
+
return {
|
|
172
|
+
directed,
|
|
173
|
+
nodeCount: staging.nodeBound,
|
|
174
|
+
edgeCount,
|
|
175
|
+
src: staging.src.view(),
|
|
176
|
+
dst: staging.dst.view(),
|
|
177
|
+
weights: staging.weight === null ? null : (staging.weight.view() as F32 | F64),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* The duplicate-policy walk of design section 6.3 step 7 over sorted rows: adjacent arcs with equal
|
|
183
|
+
* colIdx form a group; "error" throws E_DUPLICATE_EDGE; the merge policies choose a survivor (the
|
|
184
|
+
* lowest edge index, or the highest for "last"), reduce the weights over the group from the staging
|
|
185
|
+
* values (f64 precision), and fold every other member into the survivor. A reducer that produces NaN
|
|
186
|
+
* (a "sum" over +Infinity and -Infinity) is the E_INVALID_WEIGHT re-check of step 8 and throws before
|
|
187
|
+
* anything is renumbered. A reduced value makes the survivor's weight explicit (the merge stored it,
|
|
188
|
+
* design section 6.5), as does an explicit weight anywhere in the group. For an undirected graph the
|
|
189
|
+
* `(u, v)` and `(v, u)` groups are one group, walked once from the row of the lower endpoint.
|
|
190
|
+
* @param sorted - the sorted core
|
|
191
|
+
* @param staging - the (compacted) staging the core was built from
|
|
192
|
+
* @param directed - the direction
|
|
193
|
+
* @param policy - a policy other than "keep"
|
|
194
|
+
* @param countMultiplicity - whether "sum" on an unweighted staging materialises multiplicities (false on a `weighted: false` builder, which never gains a weight array)
|
|
195
|
+
* @param builderIndex - compacted edge index -> the builder's own index, for error details
|
|
196
|
+
* @returns the walk, or null when no group had more than one member
|
|
197
|
+
*/
|
|
198
|
+
function walkDuplicates(
|
|
199
|
+
sorted: SortResult,
|
|
200
|
+
staging: Staging,
|
|
201
|
+
directed: boolean,
|
|
202
|
+
policy: Exclude<DuplicatePolicy, "keep">,
|
|
203
|
+
countMultiplicity: boolean,
|
|
204
|
+
builderIndex: (e: number) => number,
|
|
205
|
+
): DuplicateWalk | null {
|
|
206
|
+
const { rowPtr, colIdx, arcToEdge } = sorted.core;
|
|
207
|
+
const nodeCount = staging.nodeBound;
|
|
208
|
+
const edgeCount = staging.edgeBound;
|
|
209
|
+
const weights = staging.weight === null ? null : staging.weight.view();
|
|
210
|
+
const survivorOf = new Uint32Array(edgeCount);
|
|
211
|
+
for (let e = 0; e < edgeCount; e++) {
|
|
212
|
+
survivorOf[e] = e;
|
|
213
|
+
}
|
|
214
|
+
let reduced: Float64Array | null = null;
|
|
215
|
+
let stored: U32 | null = null;
|
|
216
|
+
let explicit: U32 | null = null;
|
|
217
|
+
let merged = 0;
|
|
218
|
+
const group: number[] = [];
|
|
219
|
+
for (let u = 0; u < nodeCount; u++) {
|
|
220
|
+
const end = rowPtr[u + 1];
|
|
221
|
+
let a = rowPtr[u];
|
|
222
|
+
while (a < end) {
|
|
223
|
+
const v = colIdx[a];
|
|
224
|
+
let b = a + 1;
|
|
225
|
+
while (b < end && colIdx[b] === v) {
|
|
226
|
+
b++;
|
|
227
|
+
}
|
|
228
|
+
if (b - a > 1 && (directed || v >= u)) {
|
|
229
|
+
group.length = 0;
|
|
230
|
+
for (let k = a; k < b; k++) {
|
|
231
|
+
group.push(arcToEdge === null ? k : arcToEdge[k]);
|
|
232
|
+
}
|
|
233
|
+
if (policy === "error") {
|
|
234
|
+
const edges = [builderIndex(group[0]), builderIndex(group[1])];
|
|
235
|
+
throw new GraphFormatError(
|
|
236
|
+
"E_DUPLICATE_EDGE",
|
|
237
|
+
`edges ${edges[0]} and ${edges[1]} both connect ${u} -> ${v}`,
|
|
238
|
+
{ source: u, target: v, edges },
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
const survivor = policy === "last" ? group[group.length - 1] : group[0];
|
|
242
|
+
let value: number | null = null;
|
|
243
|
+
if (policy === "sum" || policy === "min" || policy === "max") {
|
|
244
|
+
value = reduceGroup(weights, group, policy, countMultiplicity);
|
|
245
|
+
if (value !== null && Number.isNaN(value)) {
|
|
246
|
+
const edges = group.map(builderIndex);
|
|
247
|
+
throw new GraphFormatError(
|
|
248
|
+
"E_INVALID_WEIGHT",
|
|
249
|
+
`duplicateEdges "${policy}" over edges ${edges.join(", ")} (${u} -> ${v}) yields NaN`,
|
|
250
|
+
{ source: u, target: v, edges, policy, reason: "reducer" },
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (value !== null) {
|
|
255
|
+
reduced ??= new Float64Array(edgeCount);
|
|
256
|
+
stored ??= new Uint32Array(bitmapWordCount(edgeCount));
|
|
257
|
+
reduced[survivor] = value;
|
|
258
|
+
bitmapSet(stored, survivor);
|
|
259
|
+
}
|
|
260
|
+
let anyExplicit = value !== null;
|
|
261
|
+
for (let i = 0; i < group.length && !anyExplicit; i++) {
|
|
262
|
+
anyExplicit = staging.weightExplicit(group[i]);
|
|
263
|
+
}
|
|
264
|
+
if (anyExplicit && !staging.weightExplicit(survivor)) {
|
|
265
|
+
explicit ??= new Uint32Array(bitmapWordCount(edgeCount));
|
|
266
|
+
bitmapSet(explicit, survivor);
|
|
267
|
+
}
|
|
268
|
+
for (const e of group) {
|
|
269
|
+
if (e !== survivor) {
|
|
270
|
+
survivorOf[e] = survivor;
|
|
271
|
+
merged++;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
a = b;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return merged === 0 ? null : { survivorOf, reduced, stored, explicit, merged };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Reduce the weights of a parallel group (design section 6.5): sum / min / max over the staging
|
|
283
|
+
* values; on an unweighted staging every weight is 1, so "sum" yields the group size (the
|
|
284
|
+
* multiplicity, when the builder may gain a weight array) and min / max leave 1 (nothing to store).
|
|
285
|
+
* @param weights - the staging weights, or null
|
|
286
|
+
* @param group - the edge indices of the group
|
|
287
|
+
* @param policy - the reducer
|
|
288
|
+
* @param countMultiplicity - whether "sum" on an unweighted staging yields the group size
|
|
289
|
+
* @returns the reduced weight (NaN when the operands cancel), or null when the survivor's own value already holds it
|
|
290
|
+
*/
|
|
291
|
+
function reduceGroup(
|
|
292
|
+
weights: ArrayLike<number> | null,
|
|
293
|
+
group: readonly number[],
|
|
294
|
+
policy: "sum" | "min" | "max",
|
|
295
|
+
countMultiplicity: boolean,
|
|
296
|
+
): number | null {
|
|
297
|
+
if (weights === null) {
|
|
298
|
+
return policy === "sum" && countMultiplicity ? group.length : null;
|
|
299
|
+
}
|
|
300
|
+
let value = weights[group[0]];
|
|
301
|
+
for (let i = 1; i < group.length; i++) {
|
|
302
|
+
const w = weights[group[i]];
|
|
303
|
+
switch (policy) {
|
|
304
|
+
case "sum":
|
|
305
|
+
value += w;
|
|
306
|
+
break;
|
|
307
|
+
case "min":
|
|
308
|
+
value = Math.min(value, w);
|
|
309
|
+
break;
|
|
310
|
+
case "max":
|
|
311
|
+
value = Math.max(value, w);
|
|
312
|
+
break;
|
|
313
|
+
default: {
|
|
314
|
+
const name: string = policy;
|
|
315
|
+
throw new GraphFormatError("E_UNSUPPORTED", `unknown reducer ${name}`, { policy: name });
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return value;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* The role-"weight" edge column of design section 3.7, when one is needed: the f64 shadow when the
|
|
324
|
+
* staging precision is f64 and some value is not f32-exact, and / or the explicit-weight record (the
|
|
325
|
+
* `weightSet` bitmap as validity) when some edge omitted its weight while another supplied it, or when
|
|
326
|
+
* a `weighted: true` builder's edges all omitted it.
|
|
327
|
+
* @param staging - the final staging
|
|
328
|
+
* @param edgeCount - the logical edge count
|
|
329
|
+
* @param weightDtype - the staging precision
|
|
330
|
+
* @returns the column, or null when the arc array already says everything
|
|
331
|
+
*/
|
|
332
|
+
function weightShadowColumn(staging: Staging, edgeCount: number, weightDtype: "f32" | "f64"): Column | null {
|
|
333
|
+
const { weight } = staging;
|
|
334
|
+
if (weight === null || edgeCount === 0) {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
const values = weight.view();
|
|
338
|
+
let inexact = false;
|
|
339
|
+
if (weightDtype === "f64") {
|
|
340
|
+
for (let e = 0; e < edgeCount; e++) {
|
|
341
|
+
const w = values[e];
|
|
342
|
+
if (Math.fround(w) !== w) {
|
|
343
|
+
inexact = true;
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const needValidity = staging.weightSet !== null || staging.weightMode === "omitted";
|
|
349
|
+
if (!inexact && !needValidity) {
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
const dtype = inexact ? "f64" : "f32";
|
|
353
|
+
const meta = resolveColumnMeta(WEIGHT_COLUMN_NAME, "edge", { dtype, role: "weight", nullable: needValidity });
|
|
354
|
+
const data = dtype === "f64" ? new Float64Array(edgeCount) : new Float32Array(edgeCount);
|
|
355
|
+
data.set(values.subarray(0, edgeCount));
|
|
356
|
+
let validity: U32 | null = null;
|
|
357
|
+
if (needValidity) {
|
|
358
|
+
validity = new Uint32Array(bitmapWordCount(edgeCount));
|
|
359
|
+
for (let e = 0; e < edgeCount; e++) {
|
|
360
|
+
if (staging.weightExplicit(e)) {
|
|
361
|
+
bitmapSet(validity, e);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return createColumn({ ...emptyParts(meta, edgeCount, validity), data });
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* The id map of design section 6.3 step 9: identity with no storage while every node is anonymous,
|
|
370
|
+
* otherwise the kind detected from the ids with the builder's `Map` and `ids` array shared by reference
|
|
371
|
+
* (decision C17).
|
|
372
|
+
* @param staging - the final staging
|
|
373
|
+
* @param nodeCount - the node count
|
|
374
|
+
* @returns the id map
|
|
375
|
+
*/
|
|
376
|
+
function idMapOf(staging: Staging, nodeCount: number): NodeIdMap {
|
|
377
|
+
if (staging.ids === null || staging.idToIndex === null) {
|
|
378
|
+
return identityNodeIdMap(nodeCount);
|
|
379
|
+
}
|
|
380
|
+
return nodeIdMapFromIds(staging.ids, nodeCount, { map: staging.idToIndex });
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Freeze a list of staging columns into a table (design section 6.3 step 10) and enforce its unique
|
|
385
|
+
* columns.
|
|
386
|
+
* @param domain - the table domain
|
|
387
|
+
* @param rowCount - the row count
|
|
388
|
+
* @param columns - the staging columns
|
|
389
|
+
* @param extra - columns to append (the weight shadow)
|
|
390
|
+
* @returns the table
|
|
391
|
+
*/
|
|
392
|
+
function tableOf(
|
|
393
|
+
domain: ColumnDomain,
|
|
394
|
+
rowCount: number,
|
|
395
|
+
columns: readonly StagingColumn[],
|
|
396
|
+
extra: readonly Column[] = [],
|
|
397
|
+
): AttributeTable {
|
|
398
|
+
const frozen: Column[] = columns.map((column) => column.toColumn(rowCount));
|
|
399
|
+
const table = new AttributeTable({ domain, rowCount, columns: [...frozen, ...extra] });
|
|
400
|
+
verifyUniqueColumns(table);
|
|
401
|
+
return table;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* The graph table (one row) from the builder's graph values.
|
|
406
|
+
* @param values - the graph values in declaration order
|
|
407
|
+
* @returns the table
|
|
408
|
+
*/
|
|
409
|
+
function graphTableOf(values: ReadonlyMap<string, GraphValue>): AttributeTable {
|
|
410
|
+
const columns: Column[] = [];
|
|
411
|
+
for (const [name, entry] of values) {
|
|
412
|
+
columns.push(columnFromValues("graph", 1, name, [entry.value], entry.decl ?? {}));
|
|
413
|
+
}
|
|
414
|
+
return new AttributeTable({ domain: "graph", rowCount: 1, columns });
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Compose the report's edge remap out of the compaction remap (old -> compacted) and the merge walk
|
|
419
|
+
* (compacted -> survivor -> final): every old edge maps to its final index, or to INVALID_INDEX when
|
|
420
|
+
* tombstoned or dropped; a merged edge maps to its survivor (design section 6.5).
|
|
421
|
+
* @param oldBound - the old edge index space
|
|
422
|
+
* @param first - the compaction remap, or null when none ran
|
|
423
|
+
* @param walk - the merge walk, or null
|
|
424
|
+
* @param second - the post-merge remap, or null
|
|
425
|
+
* @returns the composed remap, or null when it is the identity
|
|
426
|
+
*/
|
|
427
|
+
function composeEdgeRemap(
|
|
428
|
+
oldBound: number,
|
|
429
|
+
first: IndexRemap | null,
|
|
430
|
+
walk: DuplicateWalk | null,
|
|
431
|
+
second: IndexRemap | null,
|
|
432
|
+
): U32 | null {
|
|
433
|
+
if (first === null && walk === null) {
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
const out = new Uint32Array(oldBound);
|
|
437
|
+
for (let e = 0; e < oldBound; e++) {
|
|
438
|
+
let index = first === null ? e : first.remap[e];
|
|
439
|
+
if (index !== INVALID_INDEX && walk !== null && second !== null) {
|
|
440
|
+
index = second.remap[walk.survivorOf[index]];
|
|
441
|
+
}
|
|
442
|
+
out[e] = index;
|
|
443
|
+
}
|
|
444
|
+
return isIdentity(out) ? null : out;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* The identity remap of a space of `count` indices (a compaction that only renumbers edges).
|
|
449
|
+
* @param count - the space size
|
|
450
|
+
* @returns the identity
|
|
451
|
+
*/
|
|
452
|
+
function identityRemap(count: number): IndexRemap {
|
|
453
|
+
const remap = new Uint32Array(count);
|
|
454
|
+
for (let i = 0; i < count; i++) {
|
|
455
|
+
remap[i] = i;
|
|
456
|
+
}
|
|
457
|
+
return { remap, origin: remap, count };
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* The E_UNSUPPORTED error for a freeze option outside its documented set (design section 12.2
|
|
462
|
+
* FreezeOptions), thrown before any staging work so the builder is untouched.
|
|
463
|
+
* @param field - the option name
|
|
464
|
+
* @param found - the value
|
|
465
|
+
* @returns the error
|
|
466
|
+
*/
|
|
467
|
+
function freezeOptionError(field: string, found: unknown): GraphFormatError {
|
|
468
|
+
return new GraphFormatError("E_UNSUPPORTED", `freeze option ${field} has an unsupported value ${String(found)}`, {
|
|
469
|
+
field,
|
|
470
|
+
found,
|
|
471
|
+
reason: "unsupported option",
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Validate every FreezeOptions field (design section 12.2): the per-freeze duplicateEdges override
|
|
477
|
+
* against the DuplicatePolicy set (a merge policy rewrites the builder, so an unknown value must
|
|
478
|
+
* never act as one), the booleans, the label and the prepare list's shape. View names are checked
|
|
479
|
+
* by `prepare()` itself.
|
|
480
|
+
* @param options - the freeze options
|
|
481
|
+
*/
|
|
482
|
+
function checkFreezeOptions(options: FreezeOptions): void {
|
|
483
|
+
if (options.duplicateEdges !== undefined) {
|
|
484
|
+
assertDuplicatePolicy("duplicateEdges", options.duplicateEdges, freezeOptionError);
|
|
485
|
+
}
|
|
486
|
+
for (const field of ["arena", "release", "profile", "checksum"] as const) {
|
|
487
|
+
const value = options[field];
|
|
488
|
+
if (value !== undefined && typeof value !== "boolean") {
|
|
489
|
+
throw freezeOptionError(field, value);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
if (options.label !== undefined && typeof options.label !== "string") {
|
|
493
|
+
throw freezeOptionError("label", options.label);
|
|
494
|
+
}
|
|
495
|
+
if (options.prepare !== undefined && !Array.isArray(options.prepare)) {
|
|
496
|
+
throw freezeOptionError("prepare", options.prepare);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// ============================================================ the pipeline
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Run the freeze pipeline of design section 6.3 over a builder's state and produce the snapshot, the
|
|
504
|
+
* FreezeReport and the staging the builder should hold afterwards. The context is never mutated: a
|
|
505
|
+
* throw (E_SELF_LOOP, E_DUPLICATE_EDGE, E_DUPLICATE_EDGE_ID, E_TOO_LARGE, E_INVALID_WEIGHT,
|
|
506
|
+
* E_COLUMN_TYPE) leaves the builder exactly as it was.
|
|
507
|
+
* @param ctx - the builder's state
|
|
508
|
+
* @param options - the freeze options
|
|
509
|
+
* @returns the snapshot, the report and the staging to adopt
|
|
510
|
+
*/
|
|
511
|
+
export function runFreeze(ctx: FreezeContext, options: FreezeOptions): FreezeOutcome {
|
|
512
|
+
const profile = options.profile === true;
|
|
513
|
+
const timings: Record<string, number> = {};
|
|
514
|
+
const start = profile ? now() : 0;
|
|
515
|
+
let mark = start;
|
|
516
|
+
const lap = (phase: string): void => {
|
|
517
|
+
if (profile) {
|
|
518
|
+
const t = now();
|
|
519
|
+
timings[phase] = (timings[phase] ?? 0) + (t - mark);
|
|
520
|
+
mark = t;
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
const { directed } = ctx;
|
|
524
|
+
const source = ctx.staging;
|
|
525
|
+
checkFreezeOptions(options);
|
|
526
|
+
const useArena = options.arena !== false;
|
|
527
|
+
const policy = options.duplicateEdges ?? ctx.options.duplicateEdges;
|
|
528
|
+
const loopPolicy = ctx.options.selfLoops;
|
|
529
|
+
const oldNodeBound = source.nodeBound;
|
|
530
|
+
const oldEdgeBound = source.edgeBound;
|
|
531
|
+
const tombstonedEdges = oldEdgeBound - source.liveEdgeCount;
|
|
532
|
+
|
|
533
|
+
// pre-checks that must not leave a half-applied freeze behind
|
|
534
|
+
if (loopPolicy === "error") {
|
|
535
|
+
checkSelfLoops(source);
|
|
536
|
+
}
|
|
537
|
+
const liveArcs = directed ? source.liveEdgeCount : 2 * source.liveEdgeCount - source.selfLoopCount;
|
|
538
|
+
if (liveArcs > MAX_COUNT) {
|
|
539
|
+
throw new GraphFormatError("E_TOO_LARGE", `arc count ${liveArcs} exceeds MAX_COUNT`, {
|
|
540
|
+
count: liveArcs,
|
|
541
|
+
max: MAX_COUNT,
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// steps 1-2: compaction of tombstones and dropped loops into a fresh staging
|
|
546
|
+
let staging = source;
|
|
547
|
+
let nodeRemap: IndexRemap | null = null;
|
|
548
|
+
let edgeRemap: IndexRemap | null = null;
|
|
549
|
+
let droppedSelfLoops = 0;
|
|
550
|
+
const dropLoops = loopPolicy === "drop" && source.selfLoopCount > 0;
|
|
551
|
+
if (source.hasTombstones || dropLoops) {
|
|
552
|
+
const nodes = remapKeepingBits(oldNodeBound, source.nodeAlive.view());
|
|
553
|
+
const edges = remapKeepingBits(oldEdgeBound, dropLoops ? liveNonLoopBits(source) : source.edgeAlive.view());
|
|
554
|
+
droppedSelfLoops = dropLoops ? source.selfLoopCount : 0;
|
|
555
|
+
staging = compactStaging(source, {
|
|
556
|
+
nodes,
|
|
557
|
+
edges,
|
|
558
|
+
edgeValues: null,
|
|
559
|
+
mergedWeights: null,
|
|
560
|
+
mergedWeightStored: null,
|
|
561
|
+
mergedWeightSet: null,
|
|
562
|
+
});
|
|
563
|
+
nodeRemap = nodes;
|
|
564
|
+
edgeRemap = edges;
|
|
565
|
+
}
|
|
566
|
+
lap("compact");
|
|
567
|
+
|
|
568
|
+
// steps 3-6: arcs and the two counting sorts
|
|
569
|
+
const nodeCount = staging.nodeBound;
|
|
570
|
+
let sorted = sortIntoCore(sortInputOf(staging, directed), useArena && policy === "keep");
|
|
571
|
+
lap("sort");
|
|
572
|
+
|
|
573
|
+
// step 7: duplicate policy
|
|
574
|
+
let walk: DuplicateWalk | null = null;
|
|
575
|
+
let mergeRemap: IndexRemap | null = null;
|
|
576
|
+
if (policy !== "keep") {
|
|
577
|
+
const compaction = edgeRemap;
|
|
578
|
+
walk = walkDuplicates(sorted, staging, directed, policy, ctx.options.weighted !== false, (e) =>
|
|
579
|
+
compaction === null ? e : compaction.origin[e],
|
|
580
|
+
);
|
|
581
|
+
if (walk !== null) {
|
|
582
|
+
const { survivorOf } = walk;
|
|
583
|
+
const edges = remapSurvivors(survivorOf);
|
|
584
|
+
let mergedWeights: Float64Array | null = null;
|
|
585
|
+
let mergedWeightStored: U32 | null = null;
|
|
586
|
+
if (walk.reduced !== null && walk.stored !== null) {
|
|
587
|
+
mergedWeights = new Float64Array(edges.count);
|
|
588
|
+
mergedWeightStored = new Uint32Array(bitmapWordCount(edges.count));
|
|
589
|
+
for (let e = 0; e < survivorOf.length; e++) {
|
|
590
|
+
if (survivorOf[e] === e && bitmapGet(walk.stored, e)) {
|
|
591
|
+
mergedWeights[edges.remap[e]] = walk.reduced[e];
|
|
592
|
+
bitmapSet(mergedWeightStored, edges.remap[e]);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
let mergedWeightSet: U32 | null = null;
|
|
597
|
+
if (walk.explicit !== null) {
|
|
598
|
+
mergedWeightSet = new Uint32Array(bitmapWordCount(edges.count));
|
|
599
|
+
for (let e = 0; e < survivorOf.length; e++) {
|
|
600
|
+
if (survivorOf[e] === e && bitmapGet(walk.explicit, e)) {
|
|
601
|
+
bitmapSet(mergedWeightSet, edges.remap[e]);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
// refersTo edge values follow the survivor (design sections 5.11 and 7.3), not the row remap
|
|
606
|
+
const edgeValues = new Uint32Array(survivorOf.length);
|
|
607
|
+
for (let e = 0; e < survivorOf.length; e++) {
|
|
608
|
+
edgeValues[e] = edges.remap[survivorOf[e]];
|
|
609
|
+
}
|
|
610
|
+
staging = compactStaging(staging, {
|
|
611
|
+
nodes: identityRemap(nodeCount),
|
|
612
|
+
edges,
|
|
613
|
+
edgeValues,
|
|
614
|
+
mergedWeights,
|
|
615
|
+
mergedWeightStored,
|
|
616
|
+
mergedWeightSet,
|
|
617
|
+
});
|
|
618
|
+
mergeRemap = edges;
|
|
619
|
+
sorted = sortIntoCore(sortInputOf(staging, directed), useArena);
|
|
620
|
+
} else if (useArena) {
|
|
621
|
+
sorted = { ...sorted, core: copyCoreIntoArena(sorted.core) };
|
|
622
|
+
}
|
|
623
|
+
lap("duplicates");
|
|
624
|
+
}
|
|
625
|
+
const edgeCount = staging.edgeBound;
|
|
626
|
+
const { core, arcCount, selfLoopCount, flags } = sorted;
|
|
627
|
+
|
|
628
|
+
// steps 8-10: weights, id map, columns
|
|
629
|
+
const shadow = weightShadowColumn(staging, edgeCount, ctx.options.weightDtype);
|
|
630
|
+
lap("weights");
|
|
631
|
+
const ids = idMapOf(staging, nodeCount);
|
|
632
|
+
lap("ids");
|
|
633
|
+
const nodes = tableOf("node", nodeCount, staging.nodeColumns);
|
|
634
|
+
const edges = tableOf("edge", edgeCount, staging.edgeColumns, shadow === null ? [] : [shadow]);
|
|
635
|
+
const graph = graphTableOf(ctx.graphValues);
|
|
636
|
+
const extensions = new Map<string, AttributeTable>();
|
|
637
|
+
for (const table of staging.extensions) {
|
|
638
|
+
extensions.set(table.name, tableOf("extension", table.rowCount, table.columns));
|
|
639
|
+
}
|
|
640
|
+
lap("columns");
|
|
641
|
+
|
|
642
|
+
// step 12: the snapshot
|
|
643
|
+
const parts: SnapshotParts = {
|
|
644
|
+
label: options.label ?? null,
|
|
645
|
+
serial: null,
|
|
646
|
+
directed,
|
|
647
|
+
nodeCount,
|
|
648
|
+
edgeCount,
|
|
649
|
+
arcCount,
|
|
650
|
+
selfLoopCount,
|
|
651
|
+
rowPtr: core.rowPtr,
|
|
652
|
+
colIdx: core.colIdx,
|
|
653
|
+
weights: core.weights,
|
|
654
|
+
arcToEdge: core.arcToEdge,
|
|
655
|
+
edgeToArc: core.edgeToArc,
|
|
656
|
+
flags,
|
|
657
|
+
ids,
|
|
658
|
+
nodes,
|
|
659
|
+
edges,
|
|
660
|
+
graph,
|
|
661
|
+
extensions,
|
|
662
|
+
meta: ctx.meta,
|
|
663
|
+
arena: core.arena,
|
|
664
|
+
checksum: options.checksum === true,
|
|
665
|
+
};
|
|
666
|
+
const snapshot = createSnapshot(parts);
|
|
667
|
+
if (options.prepare !== undefined && options.prepare.length > 0) {
|
|
668
|
+
snapshot.prepare(options.prepare);
|
|
669
|
+
}
|
|
670
|
+
lap("snapshot");
|
|
671
|
+
if (profile) {
|
|
672
|
+
timings.total = now() - start;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const nodeRemapOut = nodeRemap !== null && !isIdentity(nodeRemap.remap) ? nodeRemap.remap : null;
|
|
676
|
+
const edgeRemapOut = composeEdgeRemap(oldEdgeBound, edgeRemap, walk, mergeRemap);
|
|
677
|
+
const mergedEdges = walk === null ? 0 : walk.merged;
|
|
678
|
+
const report: FreezeReport = Object.freeze({
|
|
679
|
+
nodeRemap: nodeRemapOut,
|
|
680
|
+
edgeRemap: edgeRemapOut,
|
|
681
|
+
compacted: nodeRemapOut !== null || edgeRemapOut !== null,
|
|
682
|
+
droppedSelfLoops,
|
|
683
|
+
mergedEdges,
|
|
684
|
+
droppedEdges: tombstonedEdges + droppedSelfLoops + mergedEdges,
|
|
685
|
+
widened: Object.freeze(ctx.widened.map((w) => Object.freeze({ ...w }))),
|
|
686
|
+
timings: Object.freeze({ ...timings }),
|
|
687
|
+
});
|
|
688
|
+
return { snapshot, report, staging };
|
|
689
|
+
}
|