Skip to content

Commit 5d2225c

Browse files
committed
refactor(graph-gen): split validation files into thematic modules
- Split structural-validators.ts (3,921 lines) into 11 thematic modules - Renamed files to remove redundant 'validators' suffix (in validation folder) - Created helper-functions.ts with shared utility functions (buildAdjacencyList, etc.) - Refactored analyzer.ts to use analyzer-types.ts and analyzer-helpers.ts - Fixed all imports to use correct module paths Module breakdown: - structural.ts: density, bipartite, tournament (3 validators) - structural-class.ts: split, cograph, claw-free, chordal, etc. (8 validators) - network.ts: scale-free, small-world, modular (3 validators) - geometric.ts: unit-disk, planar (2 validators) - path-cycle.ts: hamiltonian, traceable, diameter, etc. (6 validators) - symmetry.ts: line, self-complementary, threshold, etc. (7 validators) - invariant.ts: hereditary class, independence number, etc. (4 validators) - spectral.ts: spectrum, algebraic connectivity, spectral radius (3 validators) - robustness.ts: toughness, integrity (2 validators) - extremal.ts: cage, moore graph, ramanujan (3 validators) - product.ts: cartesian, tensor, strong, lexicographic products (4 validators) - minor.ts: minor-free, topological-minor-free (2 validators) Total: 47 validators across 12 focused modules File size reduction: structural-validators.ts 3,921 → 315 lines (92% reduction)
1 parent deedc9b commit 5d2225c

19 files changed

+4544
-4127
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* Analyzer Types and Helpers - Main Export
3+
*
4+
* This file aggregates and re-exports all analyzer types, policies, and helpers.
5+
* Individual modules are not meant to be imported directly.
6+
*/
7+
8+
export type {
9+
AnalyzerVertexId,
10+
AnalyzerVertex,
11+
AnalyzerEdge,
12+
AnalyzerGraph,
13+
ComputePolicy
14+
} from './analyzer-types';
15+
16+
export {
17+
defaultComputePolicy,
18+
unique,
19+
allEqual,
20+
edgeKeyBinary,
21+
hasAnyDirectedEdges,
22+
hasAnyUndirectedEdges,
23+
countSelfLoopsBinary,
24+
buildAdjUndirectedBinary,
25+
isConnectedUndirectedBinary,
26+
isAcyclicDirectedBinary,
27+
degreesUndirectedBinary,
28+
isBipartiteUndirectedBinary
29+
} from './analyzer-types';
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
/**
2+
* Analyzer Types and Helpers
3+
*
4+
* Shared types, compute policy, and helper functions for graph analysis.
5+
*/
6+
7+
// ============================================================================
8+
// Graph Model (namespaced to avoid conflicts with types/graph.ts)
9+
// ============================================================================
10+
11+
export type AnalyzerVertexId = string;
12+
13+
export type AnalyzerVertex = {
14+
id: AnalyzerVertexId;
15+
label?: string;
16+
attrs?: Record<string, unknown>;
17+
};
18+
19+
export type AnalyzerEdge = {
20+
id: string;
21+
endpoints: readonly AnalyzerVertexId[]; // binary => length 2; hyper => length k
22+
directed: boolean;
23+
24+
weight?: number;
25+
sign?: -1 | 1;
26+
probability?: number;
27+
label?: string;
28+
attrs?: Record<string, unknown>;
29+
};
30+
31+
export type AnalyzerGraph = {
32+
vertices: AnalyzerVertex[];
33+
edges: AnalyzerEdge[];
34+
};
35+
36+
// ============================================================================
37+
// Compute Policy (conventions for metadata-backed axes)
38+
// ============================================================================
39+
40+
export type ComputePolicy = Readonly<{
41+
vertexOrderKey: string; // vertex.attrs[vertexOrderKey] => number for ordering
42+
edgeOrderKey: string; // edge.attrs[edgeOrderKey] => number for edge ordering
43+
posKey: string; // vertex.attrs[posKey] => {x,y,(z)} for spatial embedding
44+
layerKey: string; // vertex/edge attrs[layerKey] indicates layers
45+
timeKey: string; // vertex/edge attrs[timeKey] indicates temporal
46+
rootKey: string; // vertex.attrs[rootKey] boolean or "rootId" group
47+
portKey: string; // vertex attrs indicates ports
48+
weightVectorKey: string; // edge.attrs[weightVectorKey] => number[]
49+
probabilityKey: string; // edge.attrs[probabilityKey] => number (if you store there)
50+
}>;
51+
52+
export const defaultComputePolicy: ComputePolicy = {
53+
vertexOrderKey: "order",
54+
edgeOrderKey: "order",
55+
posKey: "pos",
56+
layerKey: "layer",
57+
timeKey: "time",
58+
rootKey: "root",
59+
portKey: "ports",
60+
weightVectorKey: "weightVector",
61+
probabilityKey: "probability",
62+
};
63+
64+
// ============================================================================
65+
// Shared Helpers
66+
// ============================================================================
67+
68+
export const unique = <T>(xs: readonly T[]): T[] => [...new Set(xs)];
69+
70+
export const allEqual = <T>(xs: readonly T[], eq: (a: T, b: T) => boolean): boolean => {
71+
if (xs.length <= 1) return true;
72+
for (let i = 1; i < xs.length; i++) if (!eq(xs[0], xs[i])) return false;
73+
return true;
74+
};
75+
76+
export const edgeKeyBinary = (u: AnalyzerVertexId, v: AnalyzerVertexId, directed: boolean): string => {
77+
if (directed) return `${u}->${v}`;
78+
return u < v ? `${u}--${v}` : `${v}--${u}`;
79+
};
80+
81+
export const hasAnyDirectedEdges = (g: AnalyzerGraph): boolean => g.edges.some(e => e.directed);
82+
83+
export const hasAnyUndirectedEdges = (g: AnalyzerGraph): boolean => g.edges.some(e => !e.directed);
84+
85+
export const countSelfLoopsBinary = (g: AnalyzerGraph): number => {
86+
let c = 0;
87+
for (const e of g.edges) {
88+
if (e.endpoints.length !== 2) continue;
89+
if (e.endpoints[0] === e.endpoints[1]) c++;
90+
}
91+
return c;
92+
};
93+
94+
export const buildAdjUndirectedBinary = (g: AnalyzerGraph): Record<AnalyzerVertexId, AnalyzerVertexId[]> => {
95+
const adj: Record<string, string[]> = {};
96+
for (const v of g.vertices) adj[v.id] = [];
97+
for (const e of g.edges) {
98+
if (e.directed) continue;
99+
if (e.endpoints.length !== 2) continue;
100+
const [a, b] = e.endpoints;
101+
adj[a].push(b);
102+
adj[b].push(a);
103+
}
104+
return adj;
105+
};
106+
107+
export const isConnectedUndirectedBinary = (g: AnalyzerGraph): boolean => {
108+
if (g.vertices.length === 0) return true;
109+
const adj = buildAdjUndirectedBinary(g);
110+
const start = g.vertices[0].id;
111+
const seen = new Set<AnalyzerVertexId>();
112+
const stack: AnalyzerVertexId[] = [start];
113+
while (stack.length > 0) {
114+
const cur = stack.pop();
115+
if (!cur) continue;
116+
if (seen.has(cur)) continue;
117+
seen.add(cur);
118+
for (const nxt of adj[cur] ?? []) if (!seen.has(nxt)) stack.push(nxt);
119+
}
120+
return seen.size === g.vertices.length;
121+
};
122+
123+
export const isAcyclicDirectedBinary = (g: AnalyzerGraph): boolean => {
124+
const verts = g.vertices.map(v => v.id);
125+
const indeg: Record<AnalyzerVertexId, number> = {};
126+
const out: Record<AnalyzerVertexId, AnalyzerVertexId[]> = {};
127+
for (const v of verts) {
128+
indeg[v] = 0;
129+
out[v] = [];
130+
}
131+
for (const e of g.edges) {
132+
if (!e.directed) continue;
133+
if (e.endpoints.length !== 2) continue;
134+
const [u, v] = e.endpoints;
135+
out[u].push(v);
136+
indeg[v] += 1;
137+
}
138+
const q: AnalyzerVertexId[] = [];
139+
for (const v of verts) if (indeg[v] === 0) q.push(v);
140+
141+
let processed = 0;
142+
while (q.length > 0) {
143+
const v = q.pop();
144+
if (!v) continue;
145+
processed++;
146+
for (const w of out[v]) {
147+
indeg[w] -= 1;
148+
if (indeg[w] === 0) q.push(w);
149+
}
150+
}
151+
return processed === verts.length;
152+
};
153+
154+
export const degreesUndirectedBinary = (g: AnalyzerGraph): number[] => {
155+
const idx: Record<AnalyzerVertexId, number> = {};
156+
g.vertices.forEach((v, i) => (idx[v.id] = i));
157+
const deg = Array.from({ length: g.vertices.length }, () => 0);
158+
for (const e of g.edges) {
159+
if (e.endpoints.length !== 2) continue;
160+
const [u, v] = e.endpoints;
161+
if (u === v) continue;
162+
// Treat both directed and undirected as total degree for this classifier
163+
deg[idx[u]] += 1;
164+
deg[idx[v]] += 1;
165+
}
166+
return deg;
167+
};
168+
169+
export const isBipartiteUndirectedBinary = (g: AnalyzerGraph): boolean => {
170+
const adj = buildAdjUndirectedBinary(g);
171+
const colour = new Map<AnalyzerVertexId, 0 | 1>();
172+
for (const v of g.vertices) {
173+
if (colour.has(v.id)) continue;
174+
const queue: AnalyzerVertexId[] = [v.id];
175+
colour.set(v.id, 0);
176+
while (queue.length > 0) {
177+
const cur = queue.shift();
178+
if (!cur) break;
179+
const c = colour.get(cur);
180+
if (c === undefined) break;
181+
for (const nxt of adj[cur] ?? []) {
182+
if (!colour.has(nxt)) {
183+
colour.set(nxt, (c ^ 1) as 0 | 1);
184+
queue.push(nxt);
185+
} else if (colour.get(nxt) === c) {
186+
return false;
187+
}
188+
}
189+
}
190+
}
191+
return true;
192+
};

0 commit comments

Comments
 (0)