From 5bbd116e3cd65dfd1fd6c0848a3d7a04d3a8c91b Mon Sep 17 00:00:00 2001 From: shaia Date: Fri, 6 Mar 2026 09:24:00 +0200 Subject: [PATCH 1/3] fix: Enforce strict grid convergence monotonicity now that CG Poisson solver is used The projection solver was already switched from Red-Black SOR to CG, which resolved non-monotonic grid convergence (17x17: 0.046, 25x25: 0.037, 33x33: 0.032). Remove the +0.02 tolerance slack from grid convergence assertions, require strict error decrease with refinement, and add convergence rate reporting. Mark ROADMAP items as resolved. --- tests/validation/test_cavity_reference.c | 23 ++++++++++++++------- tests/validation/test_ghia_projection_cpu.c | 8 ++++--- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/tests/validation/test_cavity_reference.c b/tests/validation/test_cavity_reference.c index 52913782..80537a66 100644 --- a/tests/validation/test_cavity_reference.c +++ b/tests/validation/test_cavity_reference.c @@ -251,7 +251,6 @@ void test_grid_convergence(void) { size_t sizes[] = {17, 25, 33}; double errors[3]; - double prev_error = 1.0; for (int i = 0; i < 3; i++) { size_t n = sizes[i]; @@ -279,18 +278,26 @@ void test_grid_convergence(void) { } printf("\n"); - /* Error should decrease or stay similar with refinement */ - TEST_ASSERT_TRUE_MESSAGE(errors[i] <= prev_error + 0.02, - "Error increased significantly with grid refinement"); - prev_error = errors[i]; + /* Error must strictly decrease with grid refinement */ + if (i > 0) { + TEST_ASSERT_TRUE_MESSAGE(errors[i] < errors[i - 1], + "Error must decrease with grid refinement (strict monotonicity)"); + } free_centerline_data(&data); cavity_context_destroy(ctx); } - /* Finest grid should have lowest error */ - TEST_ASSERT_TRUE_MESSAGE(errors[2] <= errors[0] + 0.02, - "Grid convergence: finest grid should have lower or similar error"); + /* Finest grid must have lowest error */ + TEST_ASSERT_TRUE_MESSAGE(errors[2] < errors[0], + "Grid convergence: finest grid must have lower error than coarsest"); + + /* Report convergence rates (informational — first-order BCs limit to ~O(h^1.5)) */ + double h[] = {1.0 / 17, 1.0 / 25, 1.0 / 33}; + for (int i = 0; i < 2; i++) { + double rate = log(errors[i] / errors[i + 1]) / log(h[i] / h[i + 1]); + printf(" Rate (%zu->%zu): %.2f\n", sizes[i], sizes[i + 1], rate); + } } /* ============================================================================ diff --git a/tests/validation/test_ghia_projection_cpu.c b/tests/validation/test_ghia_projection_cpu.c index 2cd9d71a..6d2b972e 100644 --- a/tests/validation/test_ghia_projection_cpu.c +++ b/tests/validation/test_ghia_projection_cpu.c @@ -85,9 +85,11 @@ void test_projection_cpu_grid_convergence(void) { } printf("\n"); - /* Error should decrease or stay similar with refinement */ - TEST_ASSERT_TRUE_MESSAGE(result.rms_u_error <= prev_rms + 0.02, - "Error increased significantly with grid refinement"); + /* Error must strictly decrease with grid refinement */ + if (i > 0) { + TEST_ASSERT_TRUE_MESSAGE(result.rms_u_error < prev_rms, + "Error must decrease with grid refinement (strict monotonicity)"); + } prev_rms = result.rms_u_error; } } From f49e02b8f1ce66431aef22993d08d639bedef6d4 Mon Sep 17 00:00:00 2001 From: shaia Date: Fri, 6 Mar 2026 09:44:23 +0200 Subject: [PATCH 2/3] fix: Use runtime OMP detection in consistency test to fix TSan CI crash CFD_ENABLE_OPENMP was never propagated to test binaries (only defined in lib/ directory scope), so the compile-time #ifndef guard always triggered, causing premature test skips and a TSan SEGV during exit. The runtime poisson_solver_backend_available() check is sufficient. --- tests/math/test_omp_consistency.c | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/tests/math/test_omp_consistency.c b/tests/math/test_omp_consistency.c index 6c32dc71..a0c9d3fc 100644 --- a/tests/math/test_omp_consistency.c +++ b/tests/math/test_omp_consistency.c @@ -121,19 +121,8 @@ void test_cg_omp_vs_scalar(void) { TEST_ASSERT_EQUAL(CFD_SUCCESS, status); TEST_ASSERT_EQUAL(POISSON_CONVERGED, stats_scalar.status); - /* OMP backend requires OpenMP - skip if not compiled in */ -#ifndef CFD_ENABLE_OPENMP - cfd_free(x_scalar); - cfd_free(x_omp); - cfd_free(x_temp); - cfd_free(rhs); - poisson_solver_destroy(solver_scalar); - TEST_IGNORE_MESSAGE("OMP test skipped: OpenMP not enabled in this build"); - return; -#endif - - bool omp_available = poisson_solver_backend_available(POISSON_BACKEND_OMP); - if (!omp_available) { + /* OMP backend requires OpenMP - skip at runtime if unavailable */ + if (!poisson_solver_backend_available(POISSON_BACKEND_OMP)) { cfd_free(x_scalar); cfd_free(x_omp); cfd_free(x_temp); @@ -245,19 +234,8 @@ void test_redblack_omp_vs_scalar(void) { TEST_ASSERT_EQUAL(CFD_SUCCESS, status); TEST_ASSERT_EQUAL(POISSON_CONVERGED, stats_scalar.status); - /* OMP backend requires OpenMP - skip if not compiled in */ -#ifndef CFD_ENABLE_OPENMP - cfd_free(x_scalar); - cfd_free(x_omp); - cfd_free(x_temp); - cfd_free(rhs); - poisson_solver_destroy(solver_scalar); - TEST_IGNORE_MESSAGE("OMP test skipped: OpenMP not enabled in this build"); - return; -#endif - - bool omp_available = poisson_solver_backend_available(POISSON_BACKEND_OMP); - if (!omp_available) { + /* OMP backend requires OpenMP - skip at runtime if unavailable */ + if (!poisson_solver_backend_available(POISSON_BACKEND_OMP)) { cfd_free(x_scalar); cfd_free(x_omp); cfd_free(x_temp); From 4c0c510005d3ac145f8cd5a3dc03c1d3aa15b280 Mon Sep 17 00:00:00 2001 From: shaia Date: Fri, 6 Mar 2026 10:02:18 +0200 Subject: [PATCH 3/3] fix: Add convergence rate reporting to Ghia projection CPU test Store errors in an array (matching test_cavity_reference.c pattern) and report convergence rates after the grid refinement loop for consistent diagnostic output across both grid convergence tests. --- tests/validation/test_ghia_projection_cpu.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/tests/validation/test_ghia_projection_cpu.c b/tests/validation/test_ghia_projection_cpu.c index 6d2b972e..df787575 100644 --- a/tests/validation/test_ghia_projection_cpu.c +++ b/tests/validation/test_ghia_projection_cpu.c @@ -60,7 +60,7 @@ void test_projection_cpu_grid_convergence(void) { printf("\n Grid convergence study...\n"); size_t sizes[] = {17, 25, 33}; - double prev_rms = 1.0; + double errors[3]; for (int i = 0; i < 3; i++) { size_t n = sizes[i]; @@ -79,18 +79,26 @@ void test_projection_cpu_grid_convergence(void) { TEST_ASSERT_TRUE_MESSAGE(result.success, result.error_msg); - printf(" %zux%zu: RMS_u=%.4f", n, n, result.rms_u_error); - if (result.rms_u_error > GHIA_TOLERANCE_MEDIUM) { + errors[i] = result.rms_u_error; + + printf(" %zux%zu: RMS_u=%.4f", n, n, errors[i]); + if (errors[i] > GHIA_TOLERANCE_MEDIUM) { printf(" [ABOVE TARGET]"); } printf("\n"); /* Error must strictly decrease with grid refinement */ if (i > 0) { - TEST_ASSERT_TRUE_MESSAGE(result.rms_u_error < prev_rms, + TEST_ASSERT_TRUE_MESSAGE(errors[i] < errors[i - 1], "Error must decrease with grid refinement (strict monotonicity)"); } - prev_rms = result.rms_u_error; + } + + /* Report convergence rates (informational — first-order BCs limit to ~O(h^1.5)) */ + double h[] = {1.0 / 17, 1.0 / 25, 1.0 / 33}; + for (int i = 0; i < 2; i++) { + double rate = log(errors[i] / errors[i + 1]) / log(h[i] / h[i + 1]); + printf(" Rate (%zu->%zu): %.2f\n", sizes[i], sizes[i + 1], rate); } }