/** * 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) * 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 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 allGlobalFaces = new ArrayList<>(); Set 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> defResult = new ArrayList<>(); List 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> fixResult = new ArrayList<>(); List allGlobalFacesFix = new ArrayList<>(); Set 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); } }