Skip to content
This repository was archived by the owner on Jun 18, 2026. It is now read-only.

Commit 283afde

Browse files
refactor: extract LaplacianBuilder to eliminate duplicate Laplacian construction
Fixes #22 — both GraphPartitioner and SpectralAnalyzer independently built the graph Laplacian (D - A) with ~40 lines of identical logic. Extracted a shared LaplacianBuilder utility class providing: - buildLaplacian(A, n) — standard Laplacian from adjacency matrix - buildLaplacian(graph, vertexList) — direct from graph - buildSubgraphLaplacian(graph, vertices) — for vertex subsets - buildNormalizedLaplacian — D^{-1/2} L D^{-1/2} - buildRandomWalkLaplacian — I - D^{-1} A - buildAdjacencyMatrix, buildDegreeVector helpers Both GraphPartitioner.spectralBisect() and SpectralAnalyzer.compute() now delegate to LaplacianBuilder, eliminating the duplication and making it trivial to add new Laplacian variants. Closes #22
1 parent 4f8f03c commit 283afde

3 files changed

Lines changed: 244 additions & 47 deletions

File tree

Gvisual/src/gvisual/GraphPartitioner.java

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -411,25 +411,8 @@ private List<List<String>> spectralBisect(List<String> vertices) {
411411
return result;
412412
}
413413

414-
Map<String, Integer> index = new HashMap<>();
415-
for (int i = 0; i < n; i++) {
416-
index.put(vertices.get(i), i);
417-
}
418-
419414
// Build Laplacian matrix for the subgraph
420-
double[][] laplacian = new double[n][n];
421-
for (int i = 0; i < n; i++) {
422-
String v = vertices.get(i);
423-
int degree = 0;
424-
for (String neighbor : graph.getNeighbors(v)) {
425-
Integer j = index.get(neighbor);
426-
if (j != null) {
427-
laplacian[i][j] = -1.0;
428-
degree++;
429-
}
430-
}
431-
laplacian[i][i] = degree;
432-
}
415+
double[][] laplacian = LaplacianBuilder.buildSubgraphLaplacian(graph, vertices);
433416

434417
// Compute Fiedler vector (eigenvector of 2nd smallest eigenvalue)
435418
double[] fiedler = computeFiedlerVector(laplacian, n);
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
package gvisual;
2+
3+
import edu.uci.ics.jung.graph.Graph;
4+
import java.util.*;
5+
6+
/**
7+
* Utility class for constructing various Laplacian matrices from a JUNG graph.
8+
*
9+
* <p>Provides three standard Laplacian forms:</p>
10+
* <ul>
11+
* <li><b>Standard Laplacian</b> (L = D − A) — used for algebraic connectivity,
12+
* Fiedler vector, spectral partitioning, and spanning tree counting.</li>
13+
* <li><b>Normalized Laplacian</b> (L_norm = D^{-1/2} L D^{-1/2}) — eigenvalues
14+
* in [0, 2], useful for spectral clustering on graphs with heterogeneous
15+
* degree distributions.</li>
16+
* <li><b>Random Walk Laplacian</b> (L_rw = D^{-1} L = I − D^{-1} A) — relates
17+
* to random walk transition probabilities; equivalent normalized form for
18+
* clustering.</li>
19+
* </ul>
20+
*
21+
* <p>Also provides helpers for extracting the adjacency matrix, degree matrix,
22+
* and building Laplacians for vertex subsets (subgraph-induced Laplacians).</p>
23+
*
24+
* @author zalenix
25+
*/
26+
public class LaplacianBuilder {
27+
28+
private LaplacianBuilder() {
29+
// utility class — no instantiation
30+
}
31+
32+
// ═════════════════════════════════════════════════════════════════
33+
// Adjacency & Degree matrices
34+
// ═════════════════════════════════════════════════════════════════
35+
36+
/**
37+
* Builds the adjacency matrix for the given graph using the provided
38+
* vertex ordering.
39+
*
40+
* @param graph the graph
41+
* @param vertexList ordered list of vertices (defines row/column mapping)
42+
* @return n×n adjacency matrix
43+
*/
44+
public static double[][] buildAdjacencyMatrix(Graph<String, edge> graph,
45+
List<String> vertexList) {
46+
int n = vertexList.size();
47+
double[][] A = new double[n][n];
48+
Map<String, Integer> indexMap = new HashMap<>();
49+
for (int i = 0; i < n; i++) {
50+
indexMap.put(vertexList.get(i), i);
51+
}
52+
53+
for (edge e : graph.getEdges()) {
54+
Collection<String> endpoints = graph.getEndpoints(e);
55+
Iterator<String> it = endpoints.iterator();
56+
String u = it.next();
57+
String v = it.hasNext() ? it.next() : u;
58+
Integer ui = indexMap.get(u);
59+
Integer vi = indexMap.get(v);
60+
if (ui != null && vi != null && !ui.equals(vi)) {
61+
A[ui][vi] = 1.0;
62+
A[vi][ui] = 1.0;
63+
}
64+
}
65+
return A;
66+
}
67+
68+
/**
69+
* Extracts the degree vector from an adjacency matrix.
70+
*
71+
* @param A adjacency matrix
72+
* @param n dimension
73+
* @return degree vector where degree[i] = sum of row i
74+
*/
75+
public static double[] buildDegreeVector(double[][] A, int n) {
76+
double[] degree = new double[n];
77+
for (int i = 0; i < n; i++) {
78+
for (int j = 0; j < n; j++) {
79+
degree[i] += A[i][j];
80+
}
81+
}
82+
return degree;
83+
}
84+
85+
// ═════════════════════════════════════════════════════════════════
86+
// Standard Laplacian (L = D − A)
87+
// ═════════════════════════════════════════════════════════════════
88+
89+
/**
90+
* Builds the standard Laplacian matrix from an adjacency matrix.
91+
*
92+
* @param A adjacency matrix
93+
* @param n dimension
94+
* @return L = D − A
95+
*/
96+
public static double[][] buildLaplacian(double[][] A, int n) {
97+
double[][] L = new double[n][n];
98+
for (int i = 0; i < n; i++) {
99+
double degree = 0.0;
100+
for (int j = 0; j < n; j++) {
101+
L[i][j] = -A[i][j];
102+
degree += A[i][j];
103+
}
104+
L[i][i] = degree;
105+
}
106+
return L;
107+
}
108+
109+
/**
110+
* Builds the standard Laplacian for a full graph using the given vertex
111+
* ordering.
112+
*
113+
* @param graph the graph
114+
* @param vertexList ordered list of vertices
115+
* @return L = D − A
116+
*/
117+
public static double[][] buildLaplacian(Graph<String, edge> graph,
118+
List<String> vertexList) {
119+
double[][] A = buildAdjacencyMatrix(graph, vertexList);
120+
return buildLaplacian(A, vertexList.size());
121+
}
122+
123+
/**
124+
* Builds the standard Laplacian for a subgraph induced by a vertex subset.
125+
* Only edges between vertices in the subset are considered.
126+
*
127+
* @param graph the full graph
128+
* @param vertices ordered subset of vertices
129+
* @return n×n Laplacian for the induced subgraph
130+
*/
131+
public static double[][] buildSubgraphLaplacian(Graph<String, edge> graph,
132+
List<String> vertices) {
133+
int n = vertices.size();
134+
Map<String, Integer> index = new HashMap<>();
135+
for (int i = 0; i < n; i++) {
136+
index.put(vertices.get(i), i);
137+
}
138+
139+
double[][] laplacian = new double[n][n];
140+
for (int i = 0; i < n; i++) {
141+
String v = vertices.get(i);
142+
int degree = 0;
143+
for (String neighbor : graph.getNeighbors(v)) {
144+
Integer j = index.get(neighbor);
145+
if (j != null) {
146+
laplacian[i][j] = -1.0;
147+
degree++;
148+
}
149+
}
150+
laplacian[i][i] = degree;
151+
}
152+
return laplacian;
153+
}
154+
155+
// ═════════════════════════════════════════════════════════════════
156+
// Normalized Laplacian (D^{-1/2} L D^{-1/2})
157+
// ═════════════════════════════════════════════════════════════════
158+
159+
/**
160+
* Builds the symmetric normalized Laplacian: L_norm = D^{-1/2} L D^{-1/2}.
161+
* Isolated vertices (degree 0) get a diagonal entry of 0.
162+
*
163+
* @param A adjacency matrix
164+
* @param n dimension
165+
* @return normalized Laplacian matrix
166+
*/
167+
public static double[][] buildNormalizedLaplacian(double[][] A, int n) {
168+
double[] degree = buildDegreeVector(A, n);
169+
double[] invSqrtDeg = new double[n];
170+
for (int i = 0; i < n; i++) {
171+
invSqrtDeg[i] = degree[i] > 0 ? 1.0 / Math.sqrt(degree[i]) : 0.0;
172+
}
173+
174+
double[][] Ln = new double[n][n];
175+
for (int i = 0; i < n; i++) {
176+
for (int j = 0; j < n; j++) {
177+
if (i == j) {
178+
Ln[i][i] = degree[i] > 0 ? 1.0 : 0.0;
179+
} else {
180+
Ln[i][j] = -A[i][j] * invSqrtDeg[i] * invSqrtDeg[j];
181+
}
182+
}
183+
}
184+
return Ln;
185+
}
186+
187+
/**
188+
* Builds the normalized Laplacian for a graph with given vertex ordering.
189+
*
190+
* @param graph the graph
191+
* @param vertexList ordered list of vertices
192+
* @return normalized Laplacian matrix
193+
*/
194+
public static double[][] buildNormalizedLaplacian(Graph<String, edge> graph,
195+
List<String> vertexList) {
196+
double[][] A = buildAdjacencyMatrix(graph, vertexList);
197+
return buildNormalizedLaplacian(A, vertexList.size());
198+
}
199+
200+
// ═════════════════════════════════════════════════════════════════
201+
// Random Walk Laplacian (L_rw = D^{-1} L = I − D^{-1} A)
202+
// ═════════════════════════════════════════════════════════════════
203+
204+
/**
205+
* Builds the random walk Laplacian: L_rw = I − D^{-1} A.
206+
* Isolated vertices (degree 0) get a diagonal entry of 0.
207+
*
208+
* @param A adjacency matrix
209+
* @param n dimension
210+
* @return random walk Laplacian matrix
211+
*/
212+
public static double[][] buildRandomWalkLaplacian(double[][] A, int n) {
213+
double[] degree = buildDegreeVector(A, n);
214+
215+
double[][] Lrw = new double[n][n];
216+
for (int i = 0; i < n; i++) {
217+
if (degree[i] > 0) {
218+
Lrw[i][i] = 1.0;
219+
for (int j = 0; j < n; j++) {
220+
if (i != j) {
221+
Lrw[i][j] = -A[i][j] / degree[i];
222+
}
223+
}
224+
}
225+
}
226+
return Lrw;
227+
}
228+
229+
/**
230+
* Builds the random walk Laplacian for a graph with given vertex ordering.
231+
*
232+
* @param graph the graph
233+
* @param vertexList ordered list of vertices
234+
* @return random walk Laplacian matrix
235+
*/
236+
public static double[][] buildRandomWalkLaplacian(Graph<String, edge> graph,
237+
List<String> vertexList) {
238+
double[][] A = buildAdjacencyMatrix(graph, vertexList);
239+
return buildRandomWalkLaplacian(A, vertexList.size());
240+
}
241+
}

Gvisual/src/gvisual/SpectralAnalyzer.java

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -242,38 +242,11 @@ public SpectralAnalyzer compute() {
242242
// ═══════════════════════════════════════════════════════════════
243243

244244
private double[][] buildAdjacencyMatrix(int n) {
245-
double[][] A = new double[n][n];
246-
Map<String, Integer> indexMap = new HashMap<String, Integer>();
247-
for (int i = 0; i < n; i++) {
248-
indexMap.put(vertexList.get(i), i);
249-
}
250-
251-
for (edge e : graph.getEdges()) {
252-
Collection<String> endpoints = graph.getEndpoints(e);
253-
Iterator<String> it = endpoints.iterator();
254-
String u = it.next();
255-
String v = it.hasNext() ? it.next() : u;
256-
Integer ui = indexMap.get(u);
257-
Integer vi = indexMap.get(v);
258-
if (ui != null && vi != null && !ui.equals(vi)) {
259-
A[ui][vi] = 1.0;
260-
A[vi][ui] = 1.0;
261-
}
262-
}
263-
return A;
245+
return LaplacianBuilder.buildAdjacencyMatrix(graph, vertexList);
264246
}
265247

266248
private double[][] buildLaplacianMatrix(double[][] A, int n) {
267-
double[][] L = new double[n][n];
268-
for (int i = 0; i < n; i++) {
269-
double degree = 0.0;
270-
for (int j = 0; j < n; j++) {
271-
L[i][j] = -A[i][j];
272-
degree += A[i][j];
273-
}
274-
L[i][i] = degree;
275-
}
276-
return L;
249+
return LaplacianBuilder.buildLaplacian(A, n);
277250
}
278251

279252
// ═══════════════════════════════════════════════════════════════

0 commit comments

Comments
 (0)