java-topology/defects/openfoam-0002/unit/OpenFoam0002Test.java
russell@unturf.com edf083b2c4 openfoam+stella: 5-MOAD scan; openfoam-0002 CWE-407, stella CLEAN
openfoam-0002: CFCFaceToCellStencil::calcCellStencil allGlobalFaces dedup
uses findIndex() O(G) linear scan inside nested forAll(cells)*forAll(faces)
loop. Developer left comment "Note:should use hashset?" — confirmed defect.
Fix: labelHashSet seen replaces findIndex, 77x op-count speedup at C=2000/F=12.

stella: all 5 MOADs CLEAN. BreakpointMap uses unordered_map O(1); TrapArray
uses std::array<uInt8,0x10000> O(1); single-threaded, no credentials, no
thread-local identity, no thundering herd.
2026-03-31 21:55:06 -04:00

184 lines
6.9 KiB
Java

/**
* CWE-407 unit test for OpenFOAM CFCFaceToCellStencil.C calcCellStencil defect.
*
* Defect: calcCellStencil() builds per-cell stencils of neighbour face global
* indices using findIndex(allGlobalFaces, nbrGlobalI) — an O(G) linear scan —
* inside nested forAll loops over cells, cell faces, and neighbour faces.
* The developer even left the comment: "Note:should use hashset?"
*
* Fix: Replace findIndex membership check with a labelHashSet (here: HashSet<Integer>)
* giving O(1) dedup per insertion.
*
* Complexity:
* Defective: O(C * F * G) where G = accumulated global faces per cell, G ~ O(F^2)
* Fixed: O(C * F) with O(1) set insertion
*
* At C=1M cells, F=12 faces: ~12x theoretical speedup.
*/
import java.util.*;
public class OpenFoam0002Test {
/**
* Defective pattern: ArrayList dedup via contains() = O(G) per check.
* Returns operation count as proxy for work done.
*/
static long calcCellStencilDefective(int nCells, int facesPerCell) {
long ops = 0;
List<Integer> allGlobalFaces = new ArrayList<>();
for (int celli = 0; celli < nCells; celli++) {
allGlobalFaces.clear();
// My faces
for (int i = 0; i < facesPerCell; i++) {
int globalI = celli * facesPerCell + i;
allGlobalFaces.add(globalI);
ops++;
}
// Neighbour faces — each face shares a neighbour cell
for (int i = 0; i < facesPerCell; i++) {
int nbrCelli = (celli + i + 1) % nCells;
for (int j = 0; j < facesPerCell; j++) {
int nbrGlobalI = nbrCelli * facesPerCell + j;
ops += allGlobalFaces.size(); // O(G) linear scan
if (!allGlobalFaces.contains(nbrGlobalI)) {
allGlobalFaces.add(nbrGlobalI);
}
}
}
}
return ops;
}
/**
* Fixed pattern: HashSet for O(1) dedup, keep ordered list for output.
* Returns operation count.
*/
static long calcCellStencilFixed(int nCells, int facesPerCell) {
long ops = 0;
List<Integer> allGlobalFaces = new ArrayList<>();
Set<Integer> seen = new HashSet<>();
for (int celli = 0; celli < nCells; celli++) {
allGlobalFaces.clear();
seen.clear();
// My faces
for (int i = 0; i < facesPerCell; i++) {
int globalI = celli * facesPerCell + i;
ops++;
if (seen.add(globalI)) {
allGlobalFaces.add(globalI);
}
}
// Neighbour faces
for (int i = 0; i < facesPerCell; i++) {
int nbrCelli = (celli + i + 1) % nCells;
for (int j = 0; j < facesPerCell; j++) {
int nbrGlobalI = nbrCelli * facesPerCell + j;
ops++; // O(1) hash set check
if (seen.add(nbrGlobalI)) {
allGlobalFaces.add(nbrGlobalI);
}
}
}
}
return ops;
}
/**
* Verify both versions produce identical stencil sets for a small mesh.
*/
static boolean verifyCorrectness(int nCells, int facesPerCell) {
// Run both and collect results
List<Set<Integer>> defResult = new ArrayList<>();
List<Integer> allGlobalFacesDef = new ArrayList<>();
for (int celli = 0; celli < nCells; celli++) {
allGlobalFacesDef.clear();
for (int i = 0; i < facesPerCell; i++) {
allGlobalFacesDef.add(celli * facesPerCell + i);
}
for (int i = 0; i < facesPerCell; i++) {
int nbrCelli = (celli + i + 1) % nCells;
for (int j = 0; j < facesPerCell; j++) {
int g = nbrCelli * facesPerCell + j;
if (!allGlobalFacesDef.contains(g)) allGlobalFacesDef.add(g);
}
}
defResult.add(new HashSet<>(allGlobalFacesDef));
}
List<Set<Integer>> fixResult = new ArrayList<>();
List<Integer> allGlobalFacesFix = new ArrayList<>();
Set<Integer> seen = new HashSet<>();
for (int celli = 0; celli < nCells; celli++) {
allGlobalFacesFix.clear();
seen.clear();
for (int i = 0; i < facesPerCell; i++) {
int g = celli * facesPerCell + i;
if (seen.add(g)) allGlobalFacesFix.add(g);
}
for (int i = 0; i < facesPerCell; i++) {
int nbrCelli = (celli + i + 1) % nCells;
for (int j = 0; j < facesPerCell; j++) {
int g = nbrCelli * facesPerCell + j;
if (seen.add(g)) allGlobalFacesFix.add(g);
}
}
fixResult.add(new HashSet<>(allGlobalFacesFix));
}
for (int i = 0; i < nCells; i++) {
if (!defResult.get(i).equals(fixResult.get(i))) {
System.err.println("MISMATCH at cell " + i);
return false;
}
}
return true;
}
public static void main(String[] args) {
System.out.println("=== OpenFOAM CFCFaceToCellStencil calcCellStencil Dedup Test ===");
// Correctness check on small mesh
boolean correct = verifyCorrectness(20, 6);
System.out.println("Correctness (nCells=20, F=6): " + (correct ? "PASS" : "FAIL"));
if (!correct) System.exit(1);
// Performance: C=2000 cells, F=12 faces-per-cell (typical hex mesh)
int nCells = 2000;
int facesPerCell = 12;
long defOps = calcCellStencilDefective(nCells, facesPerCell);
long fixOps = calcCellStencilFixed(nCells, facesPerCell);
double ratio = (double) defOps / fixOps;
System.out.println("Cells: " + nCells + ", faces-per-cell: " + facesPerCell);
System.out.println("Defective op-count: " + defOps);
System.out.println("Fixed op-count: " + fixOps);
System.out.printf ("Ratio (defective/fixed): %.1fx%n", ratio);
// Wall-clock timing
long t0 = System.nanoTime();
for (int r = 0; r < 3; r++) calcCellStencilDefective(nCells, facesPerCell);
long defMs = (System.nanoTime() - t0) / 1_000_000 / 3;
long t1 = System.nanoTime();
for (int r = 0; r < 3; r++) calcCellStencilFixed(nCells, facesPerCell);
long fixMs = (System.nanoTime() - t1) / 1_000_000 / 3;
double wallRatio = defMs > 0 && fixMs > 0 ? (double) defMs / fixMs : ratio;
System.out.printf("Wall time — defective: %dms fixed: %dms ratio: %.1fx%n",
defMs, fixMs, wallRatio);
boolean pass = ratio >= 5.0;
System.out.println("RESULT: " + (pass ? "PASS" : "FAIL")
+ " (op-count ratio >= 5.0 required)");
if (!pass) System.exit(1);
}
}