Skip to content

Commit bac9f3d

Browse files
committed
refactor(algorithms): extract helpers from edge-generator.ts
Extracted repetitive patterns into helper modules: - structure-handlers.ts: spectral/robustness/extremal/product handlers - density-helpers.ts: exact structure checks and density calculations Reduced edge-generator.ts from 1,054 → 717 lines (32% reduction). All 580 tests passing.
1 parent 75b6b0a commit bac9f3d

File tree

3 files changed

+344
-402
lines changed

3 files changed

+344
-402
lines changed
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import type { GraphSpec } from '../spec';
2+
import type { TestEdge, TestNode } from './types';
3+
import { findComponents } from './validation-helpers';
4+
5+
/**
6+
* Check if a graph specification has an exact structure that should not be modified by density edges.
7+
* Graphs with exact structural definitions should not have additional random edges added.
8+
*/
9+
export const hasExactStructure = (spec: GraphSpec): boolean => {
10+
// Graphs with exact edge structures
11+
if (spec.completeBipartite?.kind === "complete_bipartite") return true;
12+
if (spec.grid?.kind === "grid") return true;
13+
if (spec.toroidal?.kind === "toroidal") return true;
14+
if (spec.star?.kind === "star") return true;
15+
if (spec.wheel?.kind === "wheel") return true;
16+
if (spec.binaryTree?.kind === "binary_tree" ||
17+
spec.binaryTree?.kind === "full_binary" ||
18+
spec.binaryTree?.kind === "complete_binary") return true;
19+
if (spec.tournament?.kind === "tournament") return true;
20+
21+
// Regularity constraints
22+
if (spec.cubic?.kind === "cubic") return true;
23+
if (spec.specificRegular?.kind === "k_regular") return true;
24+
25+
// Connectivity constraints
26+
if (spec.flowNetwork?.kind === "flow_network") return true;
27+
if (spec.eulerian?.kind === "eulerian" || spec.eulerian?.kind === "semi_eulerian") return true;
28+
if (spec.kVertexConnected?.kind === "k_vertex_connected") return true;
29+
if (spec.kEdgeConnected?.kind === "k_edge_connected") return true;
30+
if (spec.treewidth?.kind === "treewidth") return true;
31+
if (spec.kColorable?.kind === "k_colorable" || spec.kColorable?.kind === "bipartite_colorable") return true;
32+
33+
// Simple structural variants
34+
if (spec.split?.kind === "split") return true;
35+
if (spec.cograph?.kind === "cograph") return true;
36+
if (spec.clawFree?.kind === "claw_free") return true;
37+
38+
// Chordal-based graph classes
39+
if (spec.chordal?.kind === "chordal") return true;
40+
if (spec.interval?.kind === "interval") return true;
41+
if (spec.permutation?.kind === "permutation") return true;
42+
if (spec.comparability?.kind === "comparability") return true;
43+
if (spec.perfect?.kind === "perfect") return true;
44+
45+
// Network science generators
46+
if (spec.scaleFree?.kind === "scale_free") return true;
47+
if (spec.smallWorld?.kind === "small_world") return true;
48+
if (spec.communityStructure?.kind === "modular") return true;
49+
if (spec.line?.kind === "line_graph") return true;
50+
if (spec.selfComplementary?.kind === "self_complementary") return true;
51+
52+
// Advanced structural graphs
53+
if (spec.threshold?.kind === "threshold") return true;
54+
if (spec.unitDisk?.kind === "unit_disk") return true;
55+
if (spec.planarity?.kind === "planar") return true;
56+
if (spec.hamiltonian?.kind === "hamiltonian") return true;
57+
if (spec.traceable?.kind === "traceable") return true;
58+
59+
// Symmetry graphs
60+
if (spec.stronglyRegular?.kind === "strongly_regular") return true;
61+
if (spec.vertexTransitive?.kind === "vertex_transitive") return true;
62+
63+
return false;
64+
};
65+
66+
/**
67+
* Calculate the maximum possible edges for a graph given its specification.
68+
* Accounts for directionality, self-loops, bipartite structure, and component structure.
69+
*/
70+
export const calculateMaxPossibleEdges = (
71+
nodes: TestNode[],
72+
edges: TestEdge[],
73+
spec: GraphSpec
74+
): number => {
75+
const n = nodes.length;
76+
const selfLoopEdges = spec.selfLoops.kind === "allowed" ? n : 0;
77+
78+
// Check if bipartite
79+
const isBipartite = spec.partiteness?.kind === "bipartite";
80+
if (isBipartite) {
81+
const leftPartition = nodes.filter((node): node is TestNode & { partition: 'left' } =>
82+
node.partition === "left"
83+
);
84+
const rightPartition = nodes.filter((node): node is TestNode & { partition: 'right' } =>
85+
node.partition === "right"
86+
);
87+
88+
return spec.directionality.kind === 'directed'
89+
? (2 * leftPartition.length * rightPartition.length) + selfLoopEdges
90+
: (leftPartition.length * rightPartition.length);
91+
}
92+
93+
// Check if disconnected with multiple components
94+
if (spec.connectivity.kind === "unconstrained") {
95+
const components = findComponents(nodes, edges, spec.directionality.kind === 'directed');
96+
if (components.length > 1) {
97+
return components.reduce((total, comp) => {
98+
const compSize = comp.length;
99+
if (spec.directionality.kind === 'directed') {
100+
return total + (compSize * (compSize - 1));
101+
} else {
102+
return total + ((compSize * (compSize - 1)) / 2);
103+
}
104+
}, 0) + selfLoopEdges;
105+
}
106+
}
107+
108+
// Default: connected graph
109+
return spec.directionality.kind === 'directed'
110+
? (n * (n - 1)) + selfLoopEdges
111+
: ((n * (n - 1)) / 2);
112+
};
113+
114+
/**
115+
* Get the target edge count based on density specification and completeness.
116+
*/
117+
export const getTargetEdgeCount = (
118+
nodes: TestNode[],
119+
edges: TestEdge[],
120+
spec: GraphSpec,
121+
maxPossibleEdges: number
122+
): number => {
123+
// Handle completeness
124+
if (spec.completeness.kind === "complete") {
125+
return maxPossibleEdges;
126+
}
127+
128+
// Handle trees (already have exactly n-1 edges)
129+
const isUndirectedTree = spec.directionality.kind === "undirected" &&
130+
spec.cycles.kind === "acyclic" &&
131+
spec.connectivity.kind === "connected";
132+
133+
if (isUndirectedTree) {
134+
return edges.length; // Don't add more edges to trees
135+
}
136+
137+
// Map density to percentage of max edges
138+
const edgePercentage: Record<string, number> = {
139+
sparse: 0.15, // 10-20% (use 15% as midpoint)
140+
moderate: 0.4, // 30-50% (use 40% as midpoint)
141+
dense: 0.7, // 60-80% (use 70% as midpoint)
142+
unconstrained: 0.4, // Default to moderate for unconstrained
143+
};
144+
145+
return Math.floor(maxPossibleEdges * edgePercentage[spec.density.kind]);
146+
};
147+
148+
/**
149+
* Check if a graph needs self-loop edges.
150+
*/
151+
export const needsSelfLoop = (nodes: TestNode[], spec: GraphSpec): boolean => {
152+
return spec.selfLoops.kind === "allowed" &&
153+
spec.completeness.kind !== "complete" &&
154+
nodes.length > 0;
155+
};
156+
157+
/**
158+
* Get the maximum attempts for edge addition loop based on density.
159+
*/
160+
export const getMaxAttempts = (edgesToAdd: number, densityKind: string): number => {
161+
const multiplier = densityKind === "dense" ? 100 : 10;
162+
return edgesToAdd * multiplier;
163+
};

0 commit comments

Comments
 (0)