nx-0002 + onos-0004: networkx kcutsets seen-list O(K²) 100x; onos UNDF stamp; count 616→617

This commit is contained in:
russell@unturf.com 2026-03-28 13:42:19 -04:00
parent ea42ae35c7
commit a8b806626f
4 changed files with 146 additions and 37 deletions

View file

@ -0,0 +1,30 @@
# UNDF: UNDF-2026-000000169
--- a/networkx/algorithms/connectivity/kcutsets.py
+++ b/networkx/algorithms/connectivity/kcutsets.py
@@ -100,8 +100,12 @@ def all_node_cuts(G, k=None, flow_func=None):
# Initialize data structures.
# Keep track of the cuts already computed so we do not repeat them.
- seen = []
+ # CWE-407 fix: `not in seen` was O(K) where K is the number of cuts
+ # found so far (list scan). Each iteration in the triple-nested loop
+ # (for x in X: for v in non_adjacent: for antichain in antichains(L):)
+ # pays this cost. Fix: use a set of frozensets for O(1) lookup.
+ # node_cut is a plain set so we freeze before inserting/checking.
+ seen = set()
...
# Check if X is a k-node-cutset
if _is_separating_set(G, X):
- seen.append(X)
+ seen.add(frozenset(X))
yield X
...
# Inside the triple-nested loop:
if len(node_cut) == k:
if x in node_cut or v in node_cut:
continue
- if node_cut not in seen:
+ frozen_cut = frozenset(node_cut)
+ if frozen_cut not in seen: # O(1) hash lookup
yield node_cut
- seen.append(node_cut)
+ seen.add(frozen_cut) # O(1) insert

View file

@ -0,0 +1,111 @@
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<int[]> 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<int[]> 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<IntArrayKey> 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");
}
}

View file

@ -1,3 +1,4 @@
# UNDF: UNDF-2026-000000595
# onos-0004: ConnectivityIntentCompiler resourcesAllocated List.contains O(R×C) → O(C) with Set
## Classification