Problem: Convection Discretization Scheme Implementation and Evaluation

The problem focuses on implementing and validating common numerical convection discretization schemes for computational fluid dynamics simulations using C++. It covers core scheme implementation, stability verification via CFL number calculation, cross-scheme accuracy comparison, and performance evaluation, with all implemented functionalities exposed via function pointers for further integration.


Input/Output: The problem receives the following input data: pre-declared global function pointer-to-pointers for upwind direction determination, 1D upwind scheme, 2D upwind scheme, 2D CFL number calculation, and 2D centered convection scheme; and numerical calculation parameters including double-type velocity values (u, v, u_max, v_max), time step dt, grid spacings dx/dy, integer grid indices i/j, 1D std::vector<double> scalar field Q, and 2D std::vector<std::vector<double>> scalar field Q. The problem generates two output categories: first, correctly assigned global function pointer-to-pointers pointing to stateless lambda implementations of the corresponding numerical schemes with built-in boundary checks and invalid input handling; second, double-type calculation results returned when the exposed function pointers are invoked with valid inputs, with 0 returned for all invalid input cases. The processing flow first executes each problem_3_xx function to complete function pointer assignment, then calls the obtained function pointers with matching input parameters to retrieve computation outputs.

Required dependencies:

Sub-steps:
step_number: 1
step_description_prompt: Please write C++ code to implement a function named problem_3_01. First define the global enum class UpwindDirection with four discrete values. The function needs to implement 1D and 2D upwind schemes, create related calculation lambdas, and assign their function pointers to pre-declared global secondary function pointers.
function_header: void problem_3_01() 
test_cases: int main() {
    // Function pointers in main
    UpwindDirection (*determine_direction_ptr)(double, double) = nullptr;
    double (*upwind_1d_ptr)(double, const std::vector<double>&, int, double) = nullptr;
    double (*upwind_2d_ptr)(double, double, const std::vector<std::vector<double>>&, int, int, double, double) = nullptr;

    // Set up pointer-to-pointer so problem_3_01 can set our function pointers
    determine_direction_pptr = &determine_direction_ptr;
    upwind_1d_pptr = &upwind_1d_ptr;
    upwind_2d_pptr = &upwind_2d_ptr;

    // Initialize problem function (it will set function pointers)
    problem_3_01();

    std::cout << "========================================\n";
    std::cout << "Problem 3-Subproblem 1: 1D and 2D Upwind Scheme Implementation\n";
    std::cout << "========================================\n";
    
    bool test1_passed = false, test2_passed = false, test3_passed = false;
    
    
    {
        std::vector<double> Q = {0.0, 1.0, 2.0, 3.0, 4.0};
        double u1 = 1.0;
        double u2 = 1e-10;  
        double u3 = 0.0;    
        double dx = 1.0;
        
        double result1 = upwind_1d_ptr(u1, Q, 2, dx);
        double result2 = upwind_1d_ptr(u2, Q, 2, dx);
        double result3 = upwind_1d_ptr(u3, Q, 2, dx);
        double result_boundary = upwind_1d_ptr(u1, Q, 0, dx);  
        
        test1_passed = (std::abs(result1 - (-1.0)) < 1e-12 &&
                       std::abs(result2 - (-1e-10)) < 1e-12 &&
                       std::abs(result3) < 1e-12 &&
                       std::abs(result_boundary) < 1e-12);
        if (test1_passed) {
            std::cout << "correct\n";
        } else {
            std::cout << "wrong\n";
        }
    }
    
    
    {
        std::vector<double> Q = {0.5, 1.5, 2.5, 3.5, 4.5};
        double u = -1.5;
        double dx1 = 1.0;
        double dx2 = 0.5;  
        double dx3 = 2.0;
        
        double result1 = upwind_1d_ptr(u, Q, 2, dx1);
        double result2 = upwind_1d_ptr(u, Q, 2, dx2);
        double result3 = upwind_1d_ptr(u, Q, 2, dx3);
        
        double expected1 = -u * (Q[3] - Q[2]) / dx1;  // -(-1.5) * (3.5 - 2.5) / 1.0 = 1.5
        double expected2 = -u * (Q[3] - Q[2]) / dx2;  // -(-1.5) * (3.5 - 2.5) / 0.5 = 3.0
        double expected3 = -u * (Q[3] - Q[2]) / dx3;  // -(-1.5) * (3.5 - 2.5) / 2.0 = 0.75
        
        
        bool scaling_ok = (std::abs(result2 - 2.0 * result1) < 1e-12 &&
                          std::abs(result3 - 0.5 * result1) < 1e-12);
        
        test2_passed = (std::abs(result1 - expected1) < 1e-12 &&
                       std::abs(result2 - expected2) < 1e-12 &&
                       std::abs(result3 - expected3) < 1e-12 &&
                       scaling_ok);
        if (test2_passed) {
            std::cout << "correct\n";
        } else {
            std::cout << "wrong\n";
        }
    }
    
    
    {
        
        std::vector<std::vector<double>> Q_uniform(3, std::vector<double>(3, 1.0));
        double u1 = 1.0, v1 = 1.0;
        double result_uniform = upwind_2d_ptr(u1, v1, Q_uniform, 1, 1, 1.0, 1.0);
        
        
        std::vector<std::vector<double>> Q_nonuniform(5, std::vector<double>(5));
        for (int i = 0; i < 5; ++i) {
            for (int j = 0; j < 5; ++j) {
                Q_nonuniform[i][j] = i + j;  
            }
        }
        double result_nonuniform = upwind_2d_ptr(1.0, 1.0, Q_nonuniform, 2, 2, 1.0, 1.0);
        double expected_nonuniform = -1.0 * (Q_nonuniform[2][2] - Q_nonuniform[1][2]) / 1.0 +
                                    -1.0 * (Q_nonuniform[2][2] - Q_nonuniform[2][1]) / 1.0;
        
        
        double result_aniso = upwind_2d_ptr(1.0, 1.0, Q_nonuniform, 2, 2, 2.0, 0.5);
        double expected_aniso = -1.0 * (Q_nonuniform[2][2] - Q_nonuniform[1][2]) / 2.0 +
                               -1.0 * (Q_nonuniform[2][2] - Q_nonuniform[2][1]) / 0.5;
        
        test3_passed = (std::abs(result_uniform) < 1e-12 &&
                       std::abs(result_nonuniform - expected_nonuniform) < 1e-12 &&
                       std::abs(result_aniso - expected_aniso) < 1e-12);
        if (test3_passed) {
            std::cout << "correct\n";
        } else {
            std::cout << "wrong\n";
        }
    }
    
    std::cout << "\n========================================\n";
    if (test1_passed && test2_passed && test3_passed) {
        std::cout << "Completely correct!\n";
    }
    std::cout << "========================================\n";
    
    return 0;
}

step_number: 2
step_description_prompt: Please use C++ to generate a function named problem_3_02. It implements a lambda to compute 2D CFL number from u_max, v_max, dt, dx, dy, handles common invalid edge cases, then assigns the lambda's function pointer to a pre-declared global double function pointer-to-pointer.
test_cases: int main() {
    // Function pointer in main
    double (*calculate_cfl_2d_ptr)(double, double, double, double, double) = nullptr;

    // Set up pointer-to-pointer so problem_3_02 can set our function pointer
    calculate_cfl_2d_pptr = &calculate_cfl_2d_ptr;

    // Initialize problem function (it will set function pointers)
    problem_3_02();

    std::cout << "========================================\n";
    std::cout << "Problem 3-Subproblem 2: Stability Verification and Performance Optimization\n";
    std::cout << "========================================\n";
    
    bool test1_passed = false, test2_passed = false, test3_passed = false;
    
    
    {
        double cfl1 = calculate_cfl_2d_ptr(1.0, 1.0, 0.5, 1.0, 1.0);
        double cfl2 = calculate_cfl_2d_ptr(1.0, 2.0, 0.5, 1.0, 0.5);  
        double cfl3 = calculate_cfl_2d_ptr(-1.0, -2.0, 0.5, 1.0, 0.5);  
        double cfl4 = calculate_cfl_2d_ptr(1e-10, 1e-10, 1.0, 1.0, 1.0);  
        
        double expected_cfl1 = 0.5;
        double expected_cfl2 = std::max(1.0 * 0.5 / 1.0, 2.0 * 0.5 / 0.5);  // max(0.5, 2.0) = 2.0
        double expected_cfl3 = expected_cfl2;  
        double expected_cfl4 = 1e-10;
        
        test1_passed = (std::abs(cfl1 - expected_cfl1) < 1e-12 &&
                       std::abs(cfl2 - expected_cfl2) < 1e-12 &&
                       std::abs(cfl3 - expected_cfl3) < 1e-12 &&
                       std::abs(cfl4 - expected_cfl4) < 1e-15);
        if (test1_passed) {
            std::cout << "correct\n";
        } else {
            std::cout << "wrong\n";
        }
    }
    
    
    {
        double cfl_advection = calculate_cfl_2d_ptr(1.0, 1.0, 1.0, 1.0, 1.0);
        
        
        double k = 0.01;
        double dt = 1.0, dx = 1.0, dy = 1.0;
        double cfl_diff_x = 2.0 * k * dt / (dx * dx);
        double cfl_diff_y = 2.0 * k * dt / (dy * dy);
        double cfl_diff = std::max(cfl_diff_x, cfl_diff_y);
        
        
        double cfl_total = std::max(cfl_advection, cfl_diff);
        
        double expected_cfl_advection = 1.0;
        double expected_cfl_diff = 2.0 * 0.01 * 1.0 / (1.0 * 1.0);  // 0.02
        
        test2_passed = (std::abs(cfl_advection - expected_cfl_advection) < 1e-12 &&
                       std::abs(cfl_diff - expected_cfl_diff) < 1e-12 &&
                       std::abs(cfl_total - std::max(expected_cfl_advection, expected_cfl_diff)) < 1e-12);
        if (test2_passed) {
            std::cout << "correct\n";
        } else {
            std::cout << "wrong\n";
        }
    }
    
    
    {
        
        std::vector<double> u_values = {0.5, 1.0, 1.5, 2.0, 2.5};
        std::vector<double> v_values = {0.5, 1.0, 1.5, 2.0, 2.5};
        std::vector<double> cfl_results;
        
        auto start = std::chrono::high_resolution_clock::now();
        for (size_t i = 0; i < u_values.size(); ++i) {
            for (size_t j = 0; j < v_values.size(); ++j) {
                double cfl = calculate_cfl_2d_ptr(u_values[i], v_values[j], 0.5, 1.0, 1.0);
                cfl_results.push_back(cfl);
            }
        }
        auto end = std::chrono::high_resolution_clock::now();
        
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
        
        
        double cfl_sum = 0.0, cfl_max = cfl_results[0], cfl_min = cfl_results[0];
        for (double cfl : cfl_results) {
            cfl_sum += cfl;
            cfl_max = std::max(cfl_max, cfl);
            cfl_min = std::min(cfl_min, cfl);
        }
        double cfl_mean = cfl_sum / cfl_results.size();
        
        
        double sum_expected = 0.0;
        for (double u : u_values) {
            for (double v : v_values) {
                sum_expected += std::max(u * 0.5 / 1.0, v * 0.5 / 1.0);
            }
        }
        double expected_mean_calc = sum_expected / (u_values.size() * v_values.size());
        
        test3_passed = (duration > 0 && duration < 1000000 &&
                       std::abs(cfl_mean - expected_mean_calc) < 1e-10 &&
                       std::abs(cfl_max - 1.25) < 1e-12 &&
                       std::abs(cfl_min - 0.25) < 1e-12);
        if (test3_passed) {
            std::cout << "correct\n";
        } else {
            std::cout << "wrong\n";
        }
    }
    
    std::cout << "\n========================================\n";
    if (test1_passed && test2_passed && test3_passed) {
        std::cout << "Completely correct!\n";
    }
    std::cout << "========================================\n";
    
    return 0;
}


step_number: 3
step_description_prompt: Please use C++ to implement a function named problem_3_03. Implement 2D first-order upwind and second-order centered convection discretization calculation lambdas, add boundary and numerical validity checks, expose their function pointers through pre-defined global pointer-to-pointer variables.
function_header: void problem_3_03() 
test_cases: int main() {
    // Function pointers in main
    double (*upwind_2d_ptr)(double, double, const std::vector<std::vector<double>>&, int, int, double, double) = nullptr;
    double (*centered_2d_ptr)(double, double, const std::vector<std::vector<double>>&, int, int, double, double) = nullptr;

    // Set up pointer-to-pointer so problem_3_03 can set our function pointers
    upwind_2d_pptr = &upwind_2d_ptr;
    centered_2d_pptr = &centered_2d_ptr;

    // Initialize problem function (it will set function pointers)
    problem_3_03();

    std::cout << "========================================\n";
    std::cout << "Problem 3-Subproblem 3: Scheme Comparison\n";
    std::cout << "========================================\n";
    
    bool test1_passed = false, test2_passed = false, test3_passed = false;
    
    {
        std::vector<std::vector<double>> Q = {
            {0.2, 1.1, 2.3},
            {0.5, 1.8, 3.2},
            {0.8, 2.5, 4.1}
        };
        double u = 1.0, v = 1.0, dx = 1.0, dy = 1.0;
        
        double result_upwind = upwind_2d_ptr(u, v, Q, 1, 1, dx, dy);
        double result_centered = centered_2d_ptr(u, v, Q, 1, 1, dx, dy);
        
        double expected_upwind = -u * (Q[1][1] - Q[0][1]) / dx - v * (Q[1][1] - Q[1][0]) / dy;
        double expected_centered = -u * (Q[2][1] - Q[0][1]) / (2.0 * dx) - v * (Q[1][2] - Q[1][0]) / (2.0 * dy);
        
        double error_upwind = std::abs(result_upwind - expected_upwind);
        double error_centered = std::abs(result_centered - expected_centered);
        double format_diff = std::abs(result_upwind - result_centered);
        
        test1_passed = (error_upwind < 1e-9 && error_centered < 1e-9 && format_diff > 0.01);
        if (test1_passed) {
            std::cout << "correct\n";
        } else {
            std::cout << "wrong\n";
        }
    }
    
    {
        std::vector<std::vector<double>> Q1 = {
            {0.1, 0.5, 1.0},
            {0.3, 1.2, 2.5},
            {0.6, 2.1, 4.0}
        };
        std::vector<std::vector<double>> Q2 = {
            {0.1, 0.3, 0.5, 0.8, 1.0},
            {0.2, 0.6, 1.0, 1.6, 2.5},
            {0.4, 1.1, 1.8, 2.9, 4.0}
        };
        
        double u = 1.0, v = 0.5;
        double result1 = upwind_2d_ptr(u, v, Q1, 1, 1, 1.0, 1.0);
        double result2 = upwind_2d_ptr(u, v, Q2, 1, 2, 0.5, 0.5);
        
        bool results_different = (std::abs(result1 - result2) > 1e-10);
        
        test2_passed = (std::isfinite(result1) && std::isfinite(result2) && results_different);
        if (test2_passed) {
            std::cout << "correct\n";
        } else {
            std::cout << "wrong\n";
        }
    }
    
    {
        std::vector<std::vector<double>> Q = {
            {0.2, 1.5, 3.8, 2.1, 4.3},
            {0.3, 1.8, 4.2, 2.5, 4.8},
            {0.5, 2.2, 5.1, 3.0, 5.5}
        };
        double u = 0.8, v = 1.2, dx = 1.0, dy = 1.0;
        
        double result_upwind = upwind_2d_ptr(u, v, Q, 1, 2, dx, dy);
        double result_centered = centered_2d_ptr(u, v, Q, 1, 2, dx, dy);
        
        double expected_upwind = -u * (Q[1][2] - Q[0][2]) / dx - v * (Q[1][2] - Q[1][1]) / dy;
        double expected_centered = -u * (Q[2][2] - Q[0][2]) / (2.0 * dx) - v * (Q[1][3] - Q[1][1]) / (2.0 * dy);
        
        double error_upwind = std::abs(result_upwind - expected_upwind);
        double error_centered = std::abs(result_centered - expected_centered);
        double format_diff = std::abs(result_upwind - result_centered);
        
        test3_passed = (error_upwind < 1e-10 && error_centered < 1e-10 && format_diff > 0.05);
        if (test3_passed) {
            std::cout << "correct\n";
        } else {
            std::cout << "wrong\n";
        }
    }
    
    std::cout << "\n========================================\n";
    if (test1_passed && test2_passed && test3_passed) {
        std::cout << "Completely correct!\n";
    }
    std::cout << "========================================\n";
    
    return 0;
}

step_number: 4
step_description_prompt: Please use C++ to implement a function named problem_3_04. This function shall define a 2D upwind convection calculation lambda to compute x and y direction advection terms, add necessary input validity checks and boundary index security, then convert the lambda to a function pointer and assign it to the existing global pointer upwind_2d_pptr.
function_header: void problem_3_04() 
test_cases: int main() {
    // Function pointer in main
    double (*upwind_2d_ptr)(double, double, const std::vector<std::vector<double>>&, int, int, double, double) = nullptr;
    
    // Set up pointer-to-pointer so problem_3_04 can set our function pointer
    upwind_2d_pptr = &upwind_2d_ptr;
    
    // Initialize problem function (it will set upwind_2d_ptr)
    problem_3_04();

    std::cout << "========================================\n";
    std::cout << "Problem 3-Subproblem 4: Scheme Performance Evaluation\n";
    std::cout << "========================================\n";
    
    bool test1_passed = false, test2_passed = false, test3_passed = false;
    
    
    {
        std::vector<std::vector<double>> Q_small(3, std::vector<double>(3, 1.0));
        std::vector<std::vector<double>> Q_large(10, std::vector<double>(10, 1.0));
        double u = 1.0, v = 1.0;
        
        double result_small = upwind_2d_ptr(u, v, Q_small, 1, 1, 1.0, 1.0);
        double result_large = upwind_2d_ptr(u, v, Q_large, 5, 5, 1.0, 1.0);
        
        
        bool consistency_ok = (std::abs(result_small - result_large) < 1e-12);
        
        test1_passed = (std::abs(result_small) < 1e-12 &&
                       std::abs(result_large) < 1e-12 &&
                       consistency_ok);
        if (test1_passed) {
            std::cout << "correct\n";
        } else {
            std::cout << "wrong\n";
        }
    }
    
    
    {
        std::vector<std::vector<double>> Q(7, std::vector<double>(7));
        for (int i = 0; i < 7; ++i) {
            for (int j = 0; j < 7; ++j) {
                Q[i][j] = (i + j) * 0.5;  
            }
        }
        double u = 1.0, v = 1.0;
        double dx = 1.0, dy = 1.0;
        
        double result_center = upwind_2d_ptr(u, v, Q, 3, 3, dx, dy);
        
        
        double expected_result = -1.0 * (3.0 - 2.5) / 1.0 - 1.0 * (3.0 - 2.5) / 1.0;  // -1.0
        
        
        double result_edge = upwind_2d_ptr(u, v, Q, 2, 2, dx, dy);
        double expected_edge = -1.0 * (2.0 - 1.5) / 1.0 - 1.0 * (2.0 - 1.5) / 1.0;  // -1.0
        
        test2_passed = (std::abs(result_center - expected_result) < 1e-12 &&
                       std::abs(result_edge - expected_edge) < 1e-12 &&
                       std::abs(result_center - result_edge) < 1e-10);
        if (test2_passed) {
            std::cout << "correct\n";
        } else {
            std::cout << "wrong\n";
        }
    }
    
    
    {
        std::vector<std::vector<double>> Q(10, std::vector<double>(10, 1.0));
        Q[5][5] = 2.0;
        double u1 = 10.0, v1 = -10.0;
        double u2 = 100.0, v2 = -100.0;  
        double dx = 1.0, dy = 1.0;
        
        double result1 = upwind_2d_ptr(u1, v1, Q, 5, 5, dx, dy);
        double result2 = upwind_2d_ptr(u2, v2, Q, 5, 5, dx, dy);
        
        
        // u=10.0>0: adv_x = -u * (Q[5][5] - Q[4][5]) / dx = -10.0 * (2.0 - 1.0) / 1.0 = -10.0
        // v=-10.0<0: adv_y = -v * (Q[5][6] - Q[5][5]) / dy = -(-10.0) * (1.0 - 2.0) / 1.0 = 10.0 * (-1.0) = -10.0
        double expected_result1 = -10.0 - 10.0;  // -20.0
        
        
        double scaling_ratio = std::abs(result2 / result1);
        double expected_scaling = 10.0;
        
        test3_passed = (std::abs(result1 - expected_result1) < 1e-10 &&
                       std::abs(result2 - expected_result1 * 10.0) < 1e-8 &&
                       std::abs(scaling_ratio - expected_scaling) < 0.1 &&
                       std::isfinite(result2));
        if (test3_passed) {
            std::cout << "correct\n";
        } else {
            std::cout << "wrong\n";
        }
    }
    
    std::cout << "\n========================================\n";
    if (test1_passed && test2_passed && test3_passed) {
        std::cout << "Completely correct!\n";
    }
    std::cout << "========================================\n";
    
    return 0;
}

General solution:

General tests: