package unit; import java.util.*; import java.util.stream.*; /** * nx-0002: NetworkX all_node_cuts — seen list O(K²) → frozenset-based Set O(K) * * In networkx/algorithms/connectivity/kcutsets.py::all_node_cuts(): * * seen = [] # list of previously yielded node-cut sets * ... * if node_cut not in seen: # O(K) linear scan over prior cuts * yield node_cut * seen.append(node_cut) # O(1) append, but membership is O(K) * * This fires inside a triple-nested loop: * for x in X: # k top-degree nodes * for v in non_adjacent: # O(V) nodes per x * for antichain in antichains(L): # up to O(2^|L|) antichains * * If K distinct cuts are accumulated, each `not in seen` is O(K). Total * cost of membership checks alone is O(K²). * * Fix: seen = set() of frozenset(node_cut). Python frozensets are hashable, * so `frozen_cut not in seen` is O(1) amortised. Yield the original set; * store the frozen copy. Behavioural contract unchanged. * * UNDF: assigned by generate_undf.py * Severity: MEDIUM */ public class NetworkXKcutsetsTest { static long cmpOps = 0; // Model: a "seen" collection that deduplicates frozenset-like integer sets. // Elements are sorted integer arrays (simulate frozensets). // SLOW: List scan — O(K) per lookup static boolean seenContainsSlow(List seen, int[] cut) { for (int[] s : seen) { cmpOps++; if (Arrays.equals(s, cut)) return true; } return false; } // FAST: HashSet with Arrays.hashCode / Arrays.equals via wrapper static class IntArrayKey { final int[] arr; IntArrayKey(int[] arr) { this.arr = arr; } @Override public int hashCode() { return Arrays.hashCode(arr); } @Override public boolean equals(Object o) { return o instanceof IntArrayKey && Arrays.equals(arr, ((IntArrayKey)o).arr); } } public static void main(String[] args) { // Simulate K distinct cuts being accumulated and checked. // For each of N iterations (antichain evaluations), a new candidate cut // is checked against `seen`. After K distinct cuts are stored, the // (K+1)-th lookup must scan all K entries in the slow path. int TOTAL_ITERS = 2000; // total antichain evaluations int DISTINCT_CUTS = 200; // number of distinct cuts to yield // Build DISTINCT_CUTS distinct sorted int[] cuts of size 3 int[][] cuts = new int[DISTINCT_CUTS][]; for (int i = 0; i < DISTINCT_CUTS; i++) { cuts[i] = new int[]{i, i + 1000, i + 2000}; } // SLOW: list-based seen, check each candidate List slowSeen = new ArrayList<>(); cmpOps = 0; for (int iter = 0; iter < TOTAL_ITERS; iter++) { int[] candidate = cuts[iter % DISTINCT_CUTS]; if (!seenContainsSlow(slowSeen, candidate)) { slowSeen.add(Arrays.copyOf(candidate, candidate.length)); } } long slowOps = cmpOps; // FAST: HashSet-based seen Set fastSeen = new HashSet<>(); long fastOps = 0; for (int iter = 0; iter < TOTAL_ITERS; iter++) { int[] candidate = cuts[iter % DISTINCT_CUTS]; IntArrayKey key = new IntArrayKey(candidate); fastOps++; // one hash lookup fastSeen.add(key); } double ratio = (double) slowOps / Math.max(fastOps, 1); System.out.printf("nx-0002 kcutsets seen: SLOW=%d cmpOps, FAST~=%d ops, ratio=%.1fx%n", slowOps, fastOps, ratio); // Verify same number of distinct cuts found if (slowSeen.size() != fastSeen.size()) { System.err.printf("FAIL: distinct cuts slow=%d fast=%d%n", slowSeen.size(), fastSeen.size()); System.exit(1); } if (ratio < 5.0) { System.err.printf("FAIL: ratio %.1f < 5x%n", ratio); System.exit(1); } System.out.println("PASS"); } }