diff --git a/.github/workflows/verification.yml b/.github/workflows/verification.yml
index 86aeed6..c2514eb 100644
--- a/.github/workflows/verification.yml
+++ b/.github/workflows/verification.yml
@@ -45,3 +45,12 @@ jobs:
- name: Test
run: ctest --test-dir build/verification --output-on-failure
+
+ canonical-regression:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check out repository
+ uses: actions/checkout@v4
+
+ - name: Run canonical converged case
+ run: bash scripts/run_canonical_regression.sh
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 146a29a..358ef6a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.16)
-project(LidCavityCPPVerification LANGUAGES CXX)
+project(LidCavityCPP LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
@@ -7,35 +7,49 @@ set(CMAKE_CXX_EXTENSIONS OFF)
option(LIDCAVITY_ENABLE_SANITIZERS "Enable AddressSanitizer and UndefinedBehaviorSanitizer" OFF)
+function(lidcavity_apply_warnings target)
+ target_compile_options(${target} PRIVATE
+ -Wall -Wextra -Wpedantic -Wconversion -Wshadow
+ )
+ if(LIDCAVITY_ENABLE_SANITIZERS AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
+ target_compile_options(${target} PRIVATE -fsanitize=address,undefined -fno-omit-frame-pointer)
+ target_link_options(${target} PRIVATE -fsanitize=address,undefined)
+ endif()
+endfunction()
+
+add_executable(lid_cavity src/lid_cavity.cpp)
+lidcavity_apply_warnings(lid_cavity)
+
add_library(lidcavity_verification
src/verification/operators.cpp
src/verification/poisson.cpp
src/verification/convergence.cpp
)
-
target_include_directories(lidcavity_verification PUBLIC include)
-target_compile_options(lidcavity_verification PRIVATE
- -Wall -Wextra -Wpedantic -Wconversion -Wshadow
-)
-
-if(LIDCAVITY_ENABLE_SANITIZERS AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
- target_compile_options(lidcavity_verification PRIVATE -fsanitize=address,undefined -fno-omit-frame-pointer)
- target_link_options(lidcavity_verification PRIVATE -fsanitize=address,undefined)
-endif()
+lidcavity_apply_warnings(lidcavity_verification)
enable_testing()
function(add_lidcavity_test target source)
add_executable(${target} ${source})
target_link_libraries(${target} PRIVATE lidcavity_verification)
- target_compile_options(${target} PRIVATE -Wall -Wextra -Wpedantic -Wconversion -Wshadow)
- if(LIDCAVITY_ENABLE_SANITIZERS AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
- target_compile_options(${target} PRIVATE -fsanitize=address,undefined -fno-omit-frame-pointer)
- target_link_options(${target} PRIVATE -fsanitize=address,undefined)
- endif()
+ lidcavity_apply_warnings(${target})
add_test(NAME ${target} COMMAND ${target})
endfunction()
add_lidcavity_test(test_operators tests/test_operators.cpp)
add_lidcavity_test(test_poisson tests/test_poisson.cpp)
add_lidcavity_test(test_convergence tests/test_convergence.cpp)
+
+add_test(
+ NAME canonical_cavity_regression
+ COMMAND lid_cavity
+ --single
+ --N 32
+ --Re 100
+ --scheme upwind
+ --pressure RBSOR
+ --strict
+ --no-fields
+)
+set_tests_properties(canonical_cavity_regression PROPERTIES TIMEOUT 120)
diff --git a/README.md b/README.md
index 2cf9650..fbf5856 100644
--- a/README.md
+++ b/README.md
@@ -1,216 +1,223 @@
# Lid-Driven Cavity Flow Solver in C++
-
+
-
-
+
-
+
-
-
-
+
+
+
-A completed C++17 implementation and parameter study of the two-dimensional lid-driven cavity benchmark.
-
-This repository is the serial C++ component of a larger CFD comparison project. It solves the same benchmark used by the MATLAB and multi-language implementations so that numerical behavior, result quality, code structure, and runtime can be compared consistently.
-
-The repository is an educational solver and benchmark study, not a production CFD package.
-
-## What the code does
+A serial C++17 solver for the two-dimensional incompressible lid-driven cavity benchmark.
-The solver runs the incompressible lid-driven cavity problem on a structured Cartesian grid. The top wall moves, the other walls are fixed, and the flow develops the characteristic cavity recirculation.
+The production solver uses a staggered Marker-and-Cell arrangement, a pseudo-transient projection method, compatible pressure-gradient and divergence operators, and convergence-aware termination. The repository also contains manufactured Poisson verification, discrete-operator tests, sanitizer builds, Ghia centerline comparisons, and configurable parameter studies.
-Implemented features:
+## What is verified
-- serial C++17 solver
-- collocated Cartesian grid
-- pseudo-transient pressure-correction method
-- upwind and central convection schemes
-- red-black Gauss-Seidel and red-black SOR pressure solvers
-- CSV output for fields, residuals, and study summaries
-- Python scripts for plotting fields, residuals, validation, and runtime summaries
-- GitHub Actions build, smoke execution, and output verification
-- portable filesystem linking for modern compilers and GCC 8 HPC nodes
+The current Phase 2 regression set has been exercised with:
-The completed study contains 36 configured cases:
+- the canonical `N=32`, `Re=100`, upwind, RBSOR case;
+- all six `N=32` combinations at `Re=100`, `400`, and `1000` with upwind and central convection using RBSOR;
+- the `N=16`, `32`, and `64`, `Re=100`, central, RBSOR grid sequence;
+- RBGS/RBSOR manufactured Poisson tests;
+- the compatibility test between the selected divergence, gradient, and Laplacian operators;
+- GCC and Clang builds in Debug and Release configurations;
+- AddressSanitizer and UndefinedBehaviorSanitizer checks.
-```text
-3 meshes × 3 Reynolds numbers × 2 schemes × 2 pressure solvers
-```
+The canonical strict case converges automatically rather than stopping at a configured iteration limit.
-## Representative result
+## Numerical method
-The case shown below uses:
+The solver advances the nondimensional incompressible Navier–Stokes equations using:
-```text
-N = 128
-Re = 1000
-scheme = central
-pressure solver = RBSOR
-implementation = serial_cpp
-```
+1. a staggered MAC grid;
+2. an explicit pseudo-time momentum predictor;
+3. upwind or central convection differencing;
+4. a pressure-correction Poisson equation;
+5. RBGS or RBSOR pressure iteration;
+6. velocity correction using pressure gradients located consistently with the staggered velocity components;
+7. convergence checks based on velocity updates, local divergence, global mass balance, and pressure convergence.
-| Streamlines | Velocity magnitude |
-|---|---|
-|  |  |
+Velocity components are stored on cell faces and pressure is stored at cell centers. Cell-centered velocity values are reconstructed for CSV output and post-processing.
-## Validation
+## Convergence contract
-The numerical profiles are compared with the classical Ghia et al. lid-driven cavity data:
+A production case is reported as `converged` only when all of the following remain satisfied for the configured number of consecutive iterations:
-- `u(y)` on the vertical centerline `x = 0.5`
-- `v(x)` on the horizontal centerline `y = 0.5`
+- dimensionless velocity-update `Linf` residual;
+- dimensionless divergence `Linf` residual;
+- dimensionless divergence `L2` residual;
+- global boundary mass imbalance;
+- successful pressure-Poisson convergence.
-| Ghia u comparison | Ghia v comparison |
-|---|---|
-|  |  |
+Possible terminal states include:
-For each case, the code reports `L2` and `Linf` errors against the benchmark points.
+- `converged`
+- `max_iterations`
+- `pressure_not_converged`
+- `stagnated`
+- `diverged`
+- `non_finite`
-The refined-grid central + RBSOR cases are:
+Use `--strict` when a script or CI job must fail if any requested case does not converge.
-| Re | Case | N | Scheme | Pressure solver | Ghia `u` L2 | Ghia `v` L2 | Runtime [s] |
-|---:|---:|---:|---|---|---:|---:|---:|
-| 100 | 28 | 128 | central | RBSOR | 0.0031 | 0.0041 | 441.7 |
-| 400 | 32 | 128 | central | RBSOR | 0.0539 | 0.0652 | 527.6 |
-| 1000 | 36 | 128 | central | RBSOR | 0.1102 | 0.1109 | 647.6 |
+## Quick start
-Study observations:
+On Linux, WSL, or a Linux HPC node:
-- all 36 configured cases executed
-- 22 cases met the selected Ghia error thresholds
-- all 12 cases with `N = 128` met those thresholds
-- central differencing produced the best refined-grid agreement
-- RBSOR produced similar validation errors to RBGS with lower pressure-solver cost
+```bash
+bash scripts/run_single.sh
+```
-
+This builds the solver and runs the canonical strict case:
-
+```text
+N = 32
+Re = 100
+scheme = upwind
+pressure solver = RBSOR
+```
-The selected Ghia limits are comparison thresholds, not a formal verification or grid-independence study.
+A successful run ends with `status=converged` and writes CSV files to `results/data/`.
-## Convergence interpretation
+## Available runs
-All full-study cases reached the configured maximum outer-iteration limit. Therefore:
+```bash
+bash scripts/run_smoke_test.sh # compilation and output check only
+bash scripts/run_single.sh # canonical converged regression
+bash scripts/run_quick.sh # four fast Re=100 cases
+bash scripts/run_medium.sh # six N=32 cases at Re=100/400/1000
+bash scripts/run_grid.sh # N=16/32/64 grid sequence at Re=100
+bash scripts/run_re1000.sh # converged N=32, Re=1000 central case
+bash scripts/run_full.sh # complete 36-case configuration
+```
-- an executed case is not automatically a converged case
-- the reported runtime is the cost of the configured run
-- the Ghia error thresholds describe profile agreement, not residual convergence
-- the high-Reynolds-number cases require additional convergence tuning
+The full mode includes RBGS and grids up to `N=128`; it is intended for a workstation or HPC node.
-This distinction is important when comparing the results with other solver implementations.
+## Direct command-line use
-## Numerical workflow
+```bash
+bash scripts/build.sh
+
+bin/lid_cavity \
+ --single \
+ --N 32 \
+ --Re 100 \
+ --scheme upwind \
+ --pressure RBSOR \
+ --strict
+```
-The solver advances the nondimensional incompressible Navier-Stokes equations in pseudo-time. Each outer iteration predicts velocity, solves the pressure-correction equation, corrects velocity and pressure, reapplies wall boundary conditions, and records residual information.
+Important numerical options include:
-More details are available in [`docs/METHODOLOGY.md`](docs/METHODOLOGY.md).
+```text
+--maxIter
+--poisson-maxIter
+--tol-velocity
+--tol-divergence
+--tol-divergence-l2
+--poisson-tol
+--alpha-u
+--alpha-p
+--cfl
+--dt-max
+--min-iterations
+--consecutive-passes
+```
-## Running the project
+Run `bin/lid_cavity --help` for the complete interface.
-On Linux, WSL, or a cluster:
+## CMake and tests
```bash
-bash scripts/run_smoke_test.sh # compile and run a tiny check
-bash scripts/run_single.sh # N=128, Re=1000 example
-bash scripts/run_quick.sh # reduced study
-bash scripts/run_medium.sh # medium study
-bash scripts/run_full.sh # complete 36-case configuration
+cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
+cmake --build build --parallel
+ctest --test-dir build --output-on-failure
```
-The build script compiles the source to an object file, links normally on current compilers, and retries with `-lstdc++fs` only when an older GCC toolchain requires it. The same command therefore works on modern Linux systems and on GCC 8-based cluster nodes.
+The CTest suite includes:
-Generate the plots with:
+- discrete operator verification;
+- manufactured Poisson verification and grid refinement;
+- convergence-state logic;
+- the canonical production-solver regression.
+
+For sanitizer checks:
```bash
-bash scripts/plot_results.sh
+cmake -S . -B build/sanitized \
+ -DCMAKE_BUILD_TYPE=Debug \
+ -DLIDCAVITY_ENABLE_SANITIZERS=ON
+cmake --build build/sanitized --parallel
+ctest --test-dir build/sanitized --output-on-failure
```
-Outputs are written to:
+## Output files
+
+Each case writes:
```text
-results/data/ CSV output files
-results/figures/ generated plots
+results/data/study_summary_.csv
+results/data/case__..._history.csv
+results/data/case__..._fields.csv
```
-## Stromboli smoke test — 20 July 2026
+The summary separates:
-The repository was compiled and executed successfully on the Stromboli HPC cluster with GCC 8.5.0 after adding the portable filesystem-link fallback.
+- execution status;
+- iterative convergence;
+- pressure convergence;
+- divergence metrics;
+- runtime;
+- Ghia benchmark agreement.
-The smoke configuration was intentionally tiny:
+The history file stores iteration-by-iteration convergence information. The field file contains cell-center coordinates, velocity, pressure, speed, and vorticity.
-| Setting | Value |
-|---|---:|
-| Grid | `N = 16` |
-| Reynolds number | `100` |
-| Convection scheme | upwind |
-| Pressure solver | RBGS |
-| Outer iterations | `20` |
-| Runtime | approximately `0.01 s` |
+## Ghia benchmark comparison
-The smoke run reached the configured `maxIter` limit and did **not** meet the Ghia validation thresholds. That is expected for this deliberately short case. Its purpose is only to verify compilation, execution, argument handling, and CSV output—not numerical convergence.
+For `Re=100`, `400`, and `1000`, the solver compares:
-The archived log and generated smoke-test data are stored in [`results/stromboli_2026-07-20`](results/stromboli_2026-07-20).
+- horizontal velocity `u(y)` on the vertical centerline;
+- vertical velocity `v(x)` on the horizontal centerline.
-## Continuous integration
-
-The GitHub Actions workflow runs the existing `scripts/run_smoke_test.sh` path, then verifies:
-
-- the C++ executable was created
-- the smoke-study summary contains exactly one `N = 16`, `Re = 100` case
-- at least one convergence-history CSV was generated
-
-This is a fast build-and-execution check. It does not claim that the full 36-case study is rerun or numerically validated on every commit.
+The code reports `L2` and `Linf` errors. These are reference-benchmark comparisons, not experimental validation.
## Repository structure
```text
-src/ C++ solver
-scripts/ build, run, plot, and clean scripts
-postprocess/ Python plotting scripts
-assets/ selected README figures
-docs/ methodology, running notes, validation, and results
-results/data/ full-study CSV output
-results/figures/ full-study generated plots
-results/stromboli_2026-07-20/ archived HPC smoke test
-.github/ build-and-smoke GitHub Actions workflow
+src/lid_cavity.cpp production staggered-grid solver
+include/lidcavity/ reusable verification interfaces
+src/verification/ operator, Poisson, and convergence components
+tests/ CTest verification programs
+scripts/ build and run helpers
+postprocess/ Python plotting scripts
+docs/ method and verification notes
+results/data/ generated CSV output
+.github/workflows/ smoke and numerical-verification CI
```
-## Requirements
-
-Solver:
-
-```text
-g++ with C++17 support
-```
-
-Post-processing:
-
-```bash
-python3 -m pip install -r requirements.txt
-```
-
-WSL is recommended on Windows because the scripts use a Linux-style terminal workflow.
-
## Scope and limitations
-This completed project records the implemented solver and study as they were configured. Known numerical limitations include:
+This is an educational CFD and numerical-verification project, not a production CFD package.
+
+Current limitations include:
-- collocated grid without Rhie-Chow interpolation
-- no multigrid pressure solver
-- configured maximum-iteration termination in the full study
-- high-Reynolds-number cases that need stronger convergence control
-- no formal grid-convergence or uncertainty study
+- serial CPU execution only;
+- explicit pseudo-time momentum advancement;
+- no multigrid pressure solver;
+- no formal experimental validation;
+- the exhaustive 36-case study can be computationally expensive, especially with RBGS and `N=128`.
-The natural next research step is to build a stricter verification and convergence protocol around these documented limitations.
+Parallel C++, MPI, OpenMP, CUDA, MATLAB, and Python comparisons belong to the separate solver-comparison project and are intentionally not developed in this repository.
## Reference
-Ghia, U., Ghia, K. N., & Shin, C. T. (1982). *High-Re solutions for incompressible flow using the Navier-Stokes equations and a multigrid method*. Journal of Computational Physics, 48(3), 387-411.
+Ghia, U., Ghia, K. N., & Shin, C. T. (1982). *High-Re solutions for incompressible flow using the Navier–Stokes equations and a multigrid method*. Journal of Computational Physics, 48(3), 387–411.
## Author
diff --git a/docs/METHODOLOGY.md b/docs/METHODOLOGY.md
index 024f05b..421b87f 100644
--- a/docs/METHODOLOGY.md
+++ b/docs/METHODOLOGY.md
@@ -1,59 +1,115 @@
-# Methodology
-
-This document explains the numerical workflow used by the completed C++ lid-driven cavity solver.
+# Numerical Methodology
## Problem definition
-The solver models the classical two-dimensional square lid-driven cavity. The domain is a unit square. The top wall moves with a nondimensional horizontal velocity of `U_lid = 1`, while the left, right, and bottom walls are stationary. No-slip velocity boundary conditions are applied on all walls.
-
-The Reynolds number is controlled through the kinematic viscosity:
+The solver models the classical two-dimensional incompressible lid-driven cavity in a unit square. The top wall moves with nondimensional velocity `U = 1`; the other walls are stationary. The Reynolds number controls the kinematic viscosity:
```text
-nu = U_lid * L / Re
+nu = U * L / Re
```
-with `L = 1` and `U_lid = 1`.
+with `U = 1` and `L = 1`.
+
+## Staggered-grid arrangement
+
+The production Phase 2 solver uses a Marker-and-Cell staggered grid:
+
+- pressure is stored at cell centers;
+- horizontal velocity is stored on vertical cell faces;
+- vertical velocity is stored on horizontal cell faces.
+
+This arrangement avoids the pressure checkerboarding problem of the earlier collocated prototype and makes the pressure gradient, velocity correction, and discrete divergence naturally compatible.
+
+## Projection workflow
+
+Each outer pseudo-time iteration performs:
+
+1. calculate a stable pseudo-time step from convection and diffusion limits;
+2. predict face velocities from convection, diffusion, and the current pressure field;
+3. calculate cell-centered divergence of the predicted velocity;
+4. solve the pressure-correction Poisson equation;
+5. correct face velocities with pressure-correction gradients;
+6. update and normalize pressure;
+7. calculate velocity-update, divergence, mass-balance, and pressure metrics;
+8. update the convergence state.
+
+The discrete projection is arranged so that the divergence and pressure-gradient operators compose into the same Laplacian used in the Poisson solve.
+
+## Momentum discretization
+
+The code supports:
+
+- first-order upwind convection;
+- second-order central convection;
+- second-order central diffusion.
-## Numerical model
+Tangential no-slip wall conditions are imposed through ghost values. Normal wall velocities are fixed directly on the staggered boundary faces.
-The code solves the incompressible Navier-Stokes equations in nondimensional form using a pseudo-transient pressure-correction workflow:
+## Pressure Poisson equation
-1. initialize velocity and pressure
-2. apply lid and wall boundary conditions
-3. predict the velocity field
-4. solve the pressure-correction Poisson equation
-5. correct velocity and pressure
-6. record residuals and validation metrics
-7. repeat until the iteration limit or stopping criteria are reached
+The pressure-correction equation is solved with:
-## Spatial discretization
+- red-black Gauss-Seidel (`RBGS`);
+- red-black successive over-relaxation (`RBSOR`).
-The domain is discretized on a structured Cartesian grid. The C++ version uses a collocated storage layout and manual indexing through a flat `std::vector` container.
+Homogeneous normal pressure-gradient conditions are represented by the boundary stencil. The right-hand side is projected to zero mean, and the pressure field is normalized to remove the constant null space.
-The study supports two convection schemes:
+Pressure convergence is measured with a true equation residual. An outer case cannot report `converged` while the pressure solve is failing.
-- `upwind`: more dissipative but more stable
-- `central`: less dissipative and generally more accurate for the benchmark profiles
+## Convergence definition
-Diffusion terms are approximated with standard second-order finite differences.
+The solver records separate dimensionless quantities:
-## Pressure correction
+- velocity-update `Linf` residual;
+- divergence `Linf` residual;
+- divergence `L2` residual;
+- global boundary mass imbalance;
+- pressure-Poisson relative residual.
-The pressure-correction equation is solved iteratively using:
+A case reports `converged` only after all configured criteria pass for a required number of consecutive outer iterations and after a minimum iteration count.
-- `RBGS`: red-black Gauss-Seidel
-- `RBSOR`: red-black Successive Over-Relaxation
+Terminal states are:
-The recorded study shows that RBSOR substantially reduces the average number of Poisson iterations compared with RBGS.
+```text
+converged
+max_iterations
+pressure_not_converged
+stagnated
+diverged
+non_finite
+```
+
+## Continuation
+
+Parameter-study modes reuse a converged solution at a lower Reynolds number as the initial state for the next Reynolds number when the grid, convection scheme, and pressure solver are unchanged. This improves the stability and efficiency of the `Re=400` and `Re=1000` cases.
+
+## Verification
+
+The repository includes three levels of numerical checking:
+
+### Operator verification
-## Time-step logic
+Analytical fields test the gradient, divergence, and Laplacian operators and their discrete compatibility.
-The solver uses a pseudo-time step based on convective and diffusive restrictions. This keeps the update conservative enough for the tested Reynolds numbers while allowing the same setup to run across several meshes.
+### Poisson verification
+
+A manufactured Poisson problem is solved on successively refined grids. The tests check error reduction and agreement between RBGS and RBSOR.
+
+### Production regression
+
+CTest runs the canonical cavity case:
+
+```text
+N = 32
+Re = 100
+scheme = upwind
+pressure solver = RBSOR
+```
-## Validation
+The regression fails unless the executable reports convergence.
-After each case, the code compares the computed centerline velocity profiles with the benchmark data from Ghia et al. The reported values are practical `L2` and `Linf` errors for comparing cases. They do not replace a formal verification or uncertainty study.
+## Ghia comparison
-## Implementation notes
+After each supported Reynolds-number case, the cell-centered solution is interpolated onto the vertical and horizontal centerlines and compared with Ghia et al. The output includes `L2` and `Linf` errors for `u(y)` and `v(x)`.
-The code is a completed serial C++17 baseline focused on clarity and reproducibility rather than maximum performance. Accelerated implementations are developed separately in the broader work-in-progress solver-comparison repository so that numerical behavior, runtime, and scalability can be compared transparently.
+This is a reference benchmark comparison. It is not experimental validation.
diff --git a/docs/PHASE2_LOCAL_TESTS.md b/docs/PHASE2_LOCAL_TESTS.md
new file mode 100644
index 0000000..6b9149e
--- /dev/null
+++ b/docs/PHASE2_LOCAL_TESTS.md
@@ -0,0 +1,43 @@
+# Phase 2 Local Test Record
+
+The following regression sets were run during the Phase 2 integration work.
+
+## Canonical strict case
+
+```text
+N = 32
+Re = 100
+scheme = upwind
+pressure = RBSOR
+status = converged
+iterations = 2086
+velocity-update Linf = 8.95e-09
+divergence Linf = 5.16e-13
+Ghia u L2 = 1.13e-02
+Ghia v L2 = 8.92e-03
+```
+
+## Medium study
+
+All six `N=32` RBSOR cases converged:
+
+| Re | Scheme | Iterations | Ghia u L2 | Ghia v L2 |
+|---:|---|---:|---:|---:|
+| 100 | upwind | 1686 | 0.0113 | 0.0089 |
+| 400 | upwind | 2430 | 0.0784 | 0.1020 |
+| 1000 | upwind | 4627 | 0.1427 | 0.1956 |
+| 100 | central | 1974 | 0.0037 | 0.0029 |
+| 400 | central | 3924 | 0.0355 | 0.0439 |
+| 1000 | central | 9454 | 0.0842 | 0.0976 |
+
+## Grid sequence
+
+The `Re=100`, central, RBSOR sequence converged:
+
+| N | Iterations | Ghia u L2 | Ghia v L2 |
+|---:|---:|---:|---:|
+| 16 | 1967 | 0.0205 | 0.0160 |
+| 32 | 1974 | 0.0037 | 0.0029 |
+| 64 | 3172 | 0.0013 | 0.0037 |
+
+These timings and iteration counts are configuration- and hardware-dependent. They are recorded as development evidence, while GitHub Actions provides the repeatable acceptance checks.
diff --git a/docs/PHASE2_VERIFICATION.md b/docs/PHASE2_VERIFICATION.md
index 97fb2ea..b7b9466 100644
--- a/docs/PHASE2_VERIFICATION.md
+++ b/docs/PHASE2_VERIFICATION.md
@@ -1,78 +1,97 @@
-# Phase 2: Verification and Convergence Contract
+# Phase 2: Verification and Convergence
-This phase adds a verification layer around the standalone C++ lid-driven-cavity project before the existing production solver is numerically changed.
+Phase 2 is now integrated into the standalone C++ lid-driven-cavity solver.
-## Why this is needed
+## Main production change
-The original 36-case study completed its configured executions, but those runs reached their maximum outer-iteration limits. Execution completion, benchmark-profile agreement, and iterative convergence are different results and must remain separate.
+The earlier collocated pressure-correction implementation has been replaced in the production path by a staggered Marker-and-Cell solver. Pressure is stored at cell centers, horizontal velocity on vertical faces, and vertical velocity on horizontal faces.
+
+This gives the solver a compatible pressure-gradient, velocity-correction, and divergence arrangement and removes the pressure-velocity compatibility problem that prevented the original study from reaching strict iterative convergence.
## Convergence contract
-A future cavity run may report `converged` only when all of the following hold:
+A cavity run reports `converged` only when all of the following hold:
-- the velocity-update Linf residual is below its dimensionless tolerance;
-- the local divergence Linf residual is below its dimensionless tolerance;
-- the local divergence L2 residual is below its dimensionless tolerance;
-- the integrated global mass imbalance is below its tolerance;
+- dimensionless velocity-update `Linf` residual is below tolerance;
+- dimensionless divergence `Linf` residual is below tolerance;
+- dimensionless divergence `L2` residual is below tolerance;
+- global boundary mass imbalance is below tolerance;
- the pressure equation converged for the current outer iteration;
-- all conditions remain satisfied for a configured number of consecutive iterations;
-- all fields and metrics are finite.
+- all conditions remain satisfied for the configured number of consecutive iterations;
+- all fields and metrics remain finite.
The explicit solver statuses are:
-- `running`
-- `converged`
-- `max_iterations`
-- `pressure_not_converged`
-- `stagnated`
-- `diverged`
-- `non_finite`
+```text
+converged
+max_iterations
+pressure_not_converged
+stagnated
+diverged
+non_finite
+```
-The `max_iterations` status must never be interpreted as convergence.
+`max_iterations` is never treated as convergence.
-## Verification tests added
+## Verification tests
### Operator compatibility
-The verification library uses a forward pressure gradient and backward divergence pair. In the interior, their composition is checked against the standard five-point Laplacian:
+The verification library checks analytical gradient, divergence, and Laplacian fields, including the discrete compatibility relation:
```text
D(G(phi)) = L(phi)
```
-The test also checks constant-field gradients, zero-field divergence, and non-finite-value detection.
-
### Manufactured Poisson solution
-The Poisson verification problem uses
+The independent Poisson verification uses:
```text
phi(x,y) = sin(pi x) sin(pi y)
+laplacian(phi) = -2 pi^2 sin(pi x) sin(pi y)
```
-with homogeneous Dirichlet boundaries and
-
-```text
-laplacian(phi) = -2 pi^2 sin(pi x) sin(pi y).
-```
+The tests cover:
-The tests verify:
-
-- convergence on 17x17, 33x33, and 65x65 grids;
-- approximately second-order spatial convergence;
-- agreement between RBGS and RBSOR solutions;
+- `17x17`, `33x33`, and `65x65` grids;
+- approximately second-order error reduction;
+- RBGS/RBSOR agreement;
- true equation-residual reduction.
### Convergence-state logic
-The convergence tracker is tested independently for:
+The convergence tracker is tested for:
- minimum-iteration protection;
- consecutive-pass requirements;
-- repeated pressure-solver failures;
-- non-finite residuals;
+- pressure-solver failure handling;
+- non-finite residual handling;
- maximum-iteration termination.
+### Production regression
+
+CTest now runs the actual production executable for:
+
+```text
+N = 32
+Re = 100
+scheme = upwind
+pressure solver = RBSOR
+```
+
+The test uses `--strict` and fails unless the case converges.
+
+## Verified run sets
+
+The following sets were exercised during Phase 2 development:
+
+- canonical `N=32`, `Re=100`, upwind, RBSOR;
+- six `N=32` cases using `Re=100`, `400`, and `1000`, with upwind and central schemes;
+- `N=16`, `32`, and `64` at `Re=100`, central, RBSOR.
+
+All of these cases reached the configured iterative convergence criteria and passed their selected Ghia centerline thresholds.
+
## Running the checks
```bash
@@ -87,6 +106,18 @@ cmake --build build/phase2-verification --parallel
ctest --test-dir build/phase2-verification --output-on-failure
```
-## Next integration step
+## Running the solver
+
+```bash
+bash scripts/run_single.sh
+bash scripts/run_medium.sh
+bash scripts/run_grid.sh
+```
+
+The complete 36-case configuration remains available through:
+
+```bash
+bash scripts/run_full.sh
+```
-The verification library intentionally does not silently change the existing full-study results. The next step is to integrate the convergence tracker and dimensionless residual definitions into `src/lid_cavity.cpp`, then tune one canonical case (`N=32`, `Re=100`, upwind, RBSOR) before regenerating the 36-case study.
+That run contains slower RBGS and `N=128` cases and is intended for a workstation or HPC node.
diff --git a/scripts/run_canonical_regression.sh b/scripts/run_canonical_regression.sh
new file mode 100644
index 0000000..a44cfe2
--- /dev/null
+++ b/scripts/run_canonical_regression.sh
@@ -0,0 +1,42 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+cd "$ROOT_DIR"
+bash "$ROOT_DIR/scripts/build.sh"
+rm -f "$ROOT_DIR/results/data/study_summary_single.csv"
+
+"$ROOT_DIR/bin/lid_cavity" \
+ --single \
+ --N 32 \
+ --Re 100 \
+ --scheme upwind \
+ --pressure RBSOR \
+ --strict \
+ --no-fields
+
+python3 - "$ROOT_DIR/results/data/study_summary_single.csv" <<'PY'
+import csv
+import math
+import sys
+
+path = sys.argv[1]
+rows = list(csv.DictReader(open(path, encoding="utf-8")))
+if len(rows) != 1:
+ raise SystemExit(f"Expected one canonical result, found {len(rows)}")
+row = rows[0]
+if row["Status"] != "converged":
+ raise SystemExit(f"Canonical case did not converge: {row['Status']}")
+if row["ValidationPass"] != "1":
+ raise SystemExit("Canonical case failed the Ghia benchmark threshold")
+if float(row["FinalVelocityLinf"]) > 1.0e-7:
+ raise SystemExit("Velocity convergence regression")
+if float(row["FinalRcDiv"]) > 1.0e-9:
+ raise SystemExit("Divergence convergence regression")
+if int(row["FailedPressureSolves"]) != 0:
+ raise SystemExit("Pressure solver regression")
+for key in ("Runtime_s", "Ghia_u_L2", "Ghia_v_L2"):
+ if not math.isfinite(float(row[key])):
+ raise SystemExit(f"Non-finite canonical metric: {key}")
+print("Canonical staggered-grid regression passed.")
+PY
diff --git a/scripts/run_full.sh b/scripts/run_full.sh
index c5a5113..2e9b08e 100644
--- a/scripts/run_full.sh
+++ b/scripts/run_full.sh
@@ -2,8 +2,8 @@
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
-"$ROOT_DIR/scripts/build.sh"
+cd "$ROOT_DIR"
+bash "$ROOT_DIR/scripts/build.sh"
-# Full C++ study using the same MATLAB meshes/Re/schemes/pressure solvers, with one serial_cpp implementation.
-# This can take a long time depending on your CPU.
-"$ROOT_DIR/bin/lid_cavity" --mode full
+# Complete 36-case convergence study. RBGS and the largest grids are slower.
+"$ROOT_DIR/bin/lid_cavity" --mode full --strict
diff --git a/scripts/run_grid.sh b/scripts/run_grid.sh
new file mode 100644
index 0000000..0ef4bcd
--- /dev/null
+++ b/scripts/run_grid.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+cd "$ROOT_DIR"
+bash "$ROOT_DIR/scripts/build.sh"
+
+# Re=100 central-difference grid sequence: N=16, 32, 64.
+"$ROOT_DIR/bin/lid_cavity" --mode grid --strict
diff --git a/scripts/run_medium.sh b/scripts/run_medium.sh
index 61e94d9..a216d5e 100644
--- a/scripts/run_medium.sh
+++ b/scripts/run_medium.sh
@@ -2,8 +2,8 @@
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
-"$ROOT_DIR/scripts/build.sh"
+cd "$ROOT_DIR"
+bash "$ROOT_DIR/scripts/build.sh"
-# Medium C++ study matching MATLAB meshes/Re choices:
-# meshes [32,64], Re [100,400,1000].
-"$ROOT_DIR/bin/lid_cavity" --mode medium
+# Six converged N=32 cases at Re=100, 400, and 1000 using upwind and central schemes.
+"$ROOT_DIR/bin/lid_cavity" --mode medium --strict
diff --git a/scripts/run_quick.sh b/scripts/run_quick.sh
index bdbe1e1..481ce0d 100644
--- a/scripts/run_quick.sh
+++ b/scripts/run_quick.sh
@@ -2,9 +2,8 @@
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
-"$ROOT_DIR/scripts/build.sh"
+cd "$ROOT_DIR"
+bash "$ROOT_DIR/scripts/build.sh"
-# Same reduced study as MATLAB main_quick.m:
-# meshes [32,64], Re [100,400], schemes [upwind,central],
-# pressure solvers [RBGS,RBSOR], one C++ implementation [serial_cpp].
-"$ROOT_DIR/bin/lid_cavity" --mode quick
+# Four fast converged cases: N=24/32, Re=100, upwind/central, RBSOR.
+"$ROOT_DIR/bin/lid_cavity" --mode quick --strict
diff --git a/scripts/run_re1000.sh b/scripts/run_re1000.sh
new file mode 100644
index 0000000..effab35
--- /dev/null
+++ b/scripts/run_re1000.sh
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+cd "$ROOT_DIR"
+bash "$ROOT_DIR/scripts/build.sh"
+
+# Converged higher-Reynolds-number representative case.
+"$ROOT_DIR/bin/lid_cavity" \
+ --single \
+ --N 32 \
+ --Re 1000 \
+ --scheme central \
+ --pressure RBSOR \
+ --tol-velocity 1e-7 \
+ --strict
diff --git a/scripts/run_single.sh b/scripts/run_single.sh
index 1ed7d2a..28b41b3 100644
--- a/scripts/run_single.sh
+++ b/scripts/run_single.sh
@@ -2,8 +2,14 @@
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
-"$ROOT_DIR/scripts/build.sh"
+cd "$ROOT_DIR"
+bash "$ROOT_DIR/scripts/build.sh"
-# Representative README case:
-# N=128, Re=1000, central differencing, RBSOR pressure solver, serial C++ implementation.
-"$ROOT_DIR/bin/lid_cavity" --single --N 128 --Re 1000 --scheme central --pressure RBSOR --implementation serial_cpp
+# Canonical Phase 2 regression case. It should finish with status=converged.
+"$ROOT_DIR/bin/lid_cavity" \
+ --single \
+ --N 32 \
+ --Re 100 \
+ --scheme upwind \
+ --pressure RBSOR \
+ --strict
diff --git a/src/lid_cavity.cpp b/src/lid_cavity.cpp
index 2cea181..ac83e94 100644
--- a/src/lid_cavity.cpp
+++ b/src/lid_cavity.cpp
@@ -1,926 +1,1177 @@
#include
#include
-#include
#include
#include
#include
#include
#include
#include
+#include