java-topology/defects/python-igraph/unit/IgraphCohesiveBlocksTest.java

187 lines
7.5 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package unit;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
/**
* IgraphCohesiveBlocksTest
*
* Models CWE-407 defect igraph-0001:
*
* python-igraph CohesiveBlocks.max_cohesion() — list membership scan
* File: src/igraph/clustering.py, line 1311
*
* Defective: self._clusters is a list-of-lists; `if idx in cluster` is
* O(|cluster|) per block, O(B×C) per call, O(V×B×C) for all
* vertices — degrades to O(V²) when B×C ~ V.
*
* Fixed: self._cluster_sets is a list-of-frozensets; `if idx in
* cluster_set` is O(1); full-pass cost is O(V×B).
*
* Tests instrument comparison counts explicitly — no wall-clock timing.
*/
public class IgraphCohesiveBlocksTest {
// -----------------------------------------------------------------------
// Model: a CohesiveBlocks object with B blocks, each of size C.
// Vertex indices are integers 0..V-1.
// We simulate max_cohesion(idx) for each vertex 0..V-1.
// -----------------------------------------------------------------------
/**
* Defective path: each cluster stored as ArrayList.
* `if idx in cluster` performs a linear scan of the list.
*
* @param numVertices V — total vertex count
* @param numBlocks B — number of cohesive blocks
* @param clusterSize C — vertices per block (each block = consecutive V IDs)
* @return total element comparisons across all V calls to max_cohesion
*/
static long defectiveMaxCohesion(int numVertices, int numBlocks, int clusterSize) {
// Build B clusters, each containing clusterSize consecutive vertex IDs
// (wrapping mod V to keep IDs in range)
List<ArrayList<Integer>> clusters = new ArrayList<>();
int[] cohesion = new int[numBlocks];
for (int b = 0; b < numBlocks; b++) {
ArrayList<Integer> cluster = new ArrayList<>();
for (int c = 0; c < clusterSize; c++) {
cluster.add((b * clusterSize + c) % numVertices);
}
clusters.add(cluster);
cohesion[b] = b + 1; // arbitrary cohesion score
}
long comparisons = 0;
// Simulate max_cohesion(idx) for every vertex (the natural full-pass use)
for (int idx = 0; idx < numVertices; idx++) {
// Defective: for each block, scan the list linearly
for (int b = 0; b < numBlocks; b++) {
ArrayList<Integer> cluster = clusters.get(b);
for (Integer member : cluster) {
comparisons++; // O(|cluster|) list scan
if (member.equals(idx)) {
break; // found — stop scanning this cluster
}
}
}
}
return comparisons;
}
/**
* Fixed path: each cluster stored as HashSet (models Python frozenset).
* `if idx in cluster_set` is O(1) average.
*
* @param numVertices V
* @param numBlocks B
* @param clusterSize C
* @return total hash lookups across all V calls to max_cohesion
*/
static long fixedMaxCohesion(int numVertices, int numBlocks, int clusterSize) {
List<HashSet<Integer>> clusterSets = new ArrayList<>();
for (int b = 0; b < numBlocks; b++) {
HashSet<Integer> set = new HashSet<>();
for (int c = 0; c < clusterSize; c++) {
set.add((b * clusterSize + c) % numVertices);
}
clusterSets.add(set);
}
long lookups = 0;
for (int idx = 0; idx < numVertices; idx++) {
for (int b = 0; b < numBlocks; b++) {
lookups++; // O(1) hash probe — CWE-407 fix
clusterSets.get(b).contains(idx);
}
}
return lookups;
}
// -----------------------------------------------------------------------
// Test 1: defective cost > fixed cost at V=100, B=20, C=10
// -----------------------------------------------------------------------
static void test1_listScanCostsMoreThanSetLookup() {
int V = 100, B = 20, C = 10;
long defectOps = defectiveMaxCohesion(V, B, C);
long fixedOps = fixedMaxCohesion(V, B, C);
System.out.printf(
"test1: V=%d B=%d C=%d defect_comparisons=%d fixed_lookups=%d%n",
V, B, C, defectOps, fixedOps);
assert defectOps > fixedOps
: "igraph-0001: list scan must do more work than set lookup; defect="
+ defectOps + " fixed=" + fixedOps;
// Fixed cost is exactly V*B (one hash probe per block per vertex)
assert fixedOps == (long) V * B
: "igraph-0001: fixed lookups should be V*B=" + ((long) V * B)
+ " got " + fixedOps;
}
// -----------------------------------------------------------------------
// Test 2: defect grows super-linearly with clusterSize; fixed does not
// Doubling C doubles defect cost (more comparisons per scan),
// but fixed cost stays constant (O(1) per lookup regardless of C).
// -----------------------------------------------------------------------
static void test2_defectGrowsWithClusterSize() {
int V = 80, B = 10;
int C1 = 8, C2 = 16; // double cluster size
long d1 = defectiveMaxCohesion(V, B, C1);
long d2 = defectiveMaxCohesion(V, B, C2);
long f1 = fixedMaxCohesion(V, B, C1);
long f2 = fixedMaxCohesion(V, B, C2);
System.out.printf(
"test2: V=%d B=%d C1=%d defect=%d fixed=%d | C2=%d defect=%d fixed=%d%n",
V, B, C1, d1, f1, C2, d2, f2);
// Defect cost grows with C; fixed cost is independent of C
assert d2 > d1
: "igraph-0001: defect comparisons must grow as cluster size increases";
assert f2 == f1
: "igraph-0001: fixed lookups must not change with cluster size; f1="
+ f1 + " f2=" + f2;
}
// -----------------------------------------------------------------------
// Test 3: at large scale (V=500, B=50, C=50) defect cost is at least
// 10x the fixed cost — demonstrating quadratic vs linear behaviour.
// -----------------------------------------------------------------------
static void test3_largeScaleSpeedup() {
int V = 500, B = 50, C = 50;
long defectOps = defectiveMaxCohesion(V, B, C);
long fixedOps = fixedMaxCohesion(V, B, C);
double ratio = (double) defectOps / Math.max(1, fixedOps);
System.out.printf(
"test3: V=%d B=%d C=%d defect=%d fixed=%d ratio=%.1fx%n",
V, B, C, defectOps, fixedOps, ratio);
assert ratio >= 10.0
: "igraph-0001: expected >= 10x speedup from set; got ratio=" + ratio;
}
// -----------------------------------------------------------------------
// Main
// -----------------------------------------------------------------------
public static void main(String[] args) {
System.out.println("=== IgraphCohesiveBlocksTest ===");
System.out.println("Modelling CWE-407: igraph-0001 — CohesiveBlocks.max_cohesion list scan");
System.out.println();
test1_listScanCostsMoreThanSetLookup();
System.out.println(" PASS test1_listScanCostsMoreThanSetLookup");
test2_defectGrowsWithClusterSize();
System.out.println(" PASS test2_defectGrowsWithClusterSize");
test3_largeScaleSpeedup();
System.out.println(" PASS test3_largeScaleSpeedup");
System.out.println();
System.out.println("3/3 PASS");
}
}