Skip to content

Commit 0d8a38a

Browse files
committed
refactor(algorithms): split generator.ts into modular structure
Break monolithic generator.ts (1,650 lines) into 5 focused modules: - node-generator.ts: Node generation with partition/type assignment - edge-generator.ts: Base structure and density edge generation - validation-helpers.ts: Graph validation helper functions - property-computers.ts: Property computation functions - generator.ts: Main API (reduced to 587 lines, 64% reduction) Also fix missing isChordalUndirectedBinary function: - Add Maximum Cardinality Search algorithm implementation - Export from analyzer/types.ts - Update advanced-structures.ts to import from types Improves maintainability and reduces cognitive load. All typecheck, test (580 tests), and build validation passing.
1 parent 8aaed56 commit 0d8a38a

File tree

7 files changed

+1709
-1070
lines changed

7 files changed

+1709
-1070
lines changed

packages/graph-gen/src/analyzer/advanced-structures.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,9 @@ import type {
1515
import {
1616
buildAdjUndirectedBinary,
1717
degreesUndirectedBinary,
18-
isBipartiteUndirectedBinary
18+
isBipartiteUndirectedBinary,
19+
isChordalUndirectedBinary
1920
} from './types';
20-
21-
// Forward declarations for helper functions used in this module
22-
declare function isChordalUndirectedBinary(g: AnalyzerGraph): boolean;
2321
/**
2422
* Compute perfect graph property.
2523
* Perfect graphs have no odd holes or odd anti-holes.

packages/graph-gen/src/analyzer/types.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,3 +190,81 @@ export const isBipartiteUndirectedBinary = (g: AnalyzerGraph): boolean => {
190190
}
191191
return true;
192192
};
193+
194+
/**
195+
* Check if an undirected binary graph is chordal using Maximum Cardinality Search.
196+
* A graph is chordal if every cycle of length >= 4 has a chord (equivalent to having a PEO).
197+
* @param g - Graph to check
198+
* @returns True if graph is chordal
199+
*/
200+
export const isChordalUndirectedBinary = (g: AnalyzerGraph): boolean => {
201+
if (g.vertices.length <= 3) return true;
202+
203+
// Maximum Cardinality Search to find PEO
204+
const vertices = g.vertices.map(v => v.id);
205+
const adj = buildAdjUndirectedBinary(g);
206+
const numbered = new Set<AnalyzerVertexId>();
207+
const order: AnalyzerVertexId[] = [];
208+
const weights: Record<AnalyzerVertexId, number> = {};
209+
210+
// Initialize weights
211+
for (const v of vertices) weights[v] = 0;
212+
213+
// MCS: repeatedly pick vertex with maximum weight among unnumbered
214+
for (let i = 0; i < vertices.length; i++) {
215+
let maxWeight = -1;
216+
let maxV: AnalyzerVertexId | null = null;
217+
218+
for (const v of vertices) {
219+
if (!numbered.has(v) && weights[v] > maxWeight) {
220+
maxWeight = weights[v];
221+
maxV = v;
222+
}
223+
}
224+
225+
if (maxV === null) break;
226+
227+
numbered.add(maxV);
228+
order.push(maxV);
229+
230+
// Update weights of unnumbered neighbors
231+
for (const nb of adj[maxV] ?? []) {
232+
if (!numbered.has(nb)) {
233+
weights[nb]++;
234+
}
235+
}
236+
}
237+
238+
// Check if this is a perfect elimination ordering
239+
// For each vertex, its later neighbors should form a clique
240+
const laterNeighbors = new Map<AnalyzerVertexId, Set<AnalyzerVertexId>>();
241+
242+
for (let i = 0; i < order.length; i++) {
243+
const v = order[i];
244+
const laterNbs = new Set<AnalyzerVertexId>();
245+
246+
// Find neighbors that appear later in ordering
247+
for (const nb of adj[v] ?? []) {
248+
const j = order.indexOf(nb);
249+
if (j > i) laterNbs.add(nb);
250+
}
251+
252+
laterNeighbors.set(v, laterNbs);
253+
}
254+
255+
// Check each set of later neighbors forms a clique
256+
for (const [, laterNbs] of laterNeighbors) {
257+
const nbs = [...laterNbs];
258+
for (let i = 0; i < nbs.length; i++) {
259+
for (let j = i + 1; j < nbs.length; j++) {
260+
// Check if nbs[i] and nbs[j] are adjacent
261+
const adjList = adj[nbs[i]] ?? [];
262+
if (!adjList.includes(nbs[j])) {
263+
return false; // Not a clique
264+
}
265+
}
266+
}
267+
}
268+
269+
return true;
270+
};

0 commit comments

Comments
 (0)