Problem: Finite Difference Solver for Topographic Wave Equation

The problem aims to implement a robust finite difference numerical framework for topographic wave equation simulations in C++, covering core computation modules, numeric exception handling, initialization logic, boundary condition processing, and stability verification to support accurate and stable geophysical flow evolution analysis.


Input/Output: The problem takes no direct input parameters when executing the three exposed void functions problem_4_01, problem_4_02, and problem_4_03. It relies on pre-declared global pointer-to-pointer variables provided by the caller: double (**calculate_c_pptr)(double, double, double), void (**forward_space_step_pptr)(std::vector<std::vector<double>>&, double, double, double), void (**initialize_step_pptr)(std::vector<double>&, const std::vector<double>&, double), and void (**apply_boundary_conditions_pptr)(std::vector<std::vector<double>>&, double, double, double, double, double), which are pre-assigned by the caller to point to caller-side function pointer storage locations.
As output, each function writes the address of its internally implemented lambda functions to the caller-side function pointer storage referenced by the corresponding global pointer-to-pointer variables. The implemented lambdas include finite difference c coefficient calculation with numerical exception (NaN, inf, overflow, underflow) handling, 1D forward finite difference step computation for 2D double grids, threshold-based vector initialization, and 2D grid boundary condition application, all integrated with numerical stability protection logic.

Required dependencies:

Sub-steps:
step_number: 1
step_description_prompt: Please write C++ code to implement a function named problem_4_01. This function implements finite difference related calculations, including two lambdas: one for calculating c coefficient with abnormal value handling, another for forward space step computing, then assign the lambda function pointers to corresponding pre-declared global pointer-to-pointer variables.
function_header: void problem_4_01() 
test_cases: int main() {
    // Function pointers in main
    double (*calculate_c_ptr)(double, double, double) = nullptr;
    void (*forward_space_step_ptr)(std::vector<std::vector<double>>&, double, double, double) = nullptr;

    // Set up pointer-to-pointer so problem_4_01 can set our function pointers
    calculate_c_pptr = &calculate_c_ptr;
    forward_space_step_pptr = &forward_space_step_ptr;

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


    


    std::cout << "========================================\n";
    std::cout << "Problem 4-Subproblem 1: Implementation of Topographic Wave Equation and Y-Axis Forward Space Scheme\n";
    std::cout << "========================================\n";
    
    bool test1_passed = false, test2_passed = false, test3_passed = false;
    
    
    {
        double r = 0.001, f = 0.0001, s = 0.001;
        double c1 = calculate_c_ptr(r, f, s);
        double c2 = calculate_c_ptr(0.0, f, s);  
        double c3 = calculate_c_ptr(r, 0.0, s);  
        double c4 = calculate_c_ptr(1e-10, 1e-12, 1e-12);  
        
        double expected_c1 = 10000.0;
        
        test1_passed = (std::abs(c1 - expected_c1) < 1e-12 &&
                       std::abs(c2) < 1e-12 &&
                       std::isfinite(c3) &&
                       std::isfinite(c4) && std::abs(c4) <= 1e10);
        if (test1_passed) {
            std::cout << "correct\n";
        } else {
            std::cout << "wrong\n";
        }
    }
    
    
    {
        double r = 0.01, f = 0.0001, s = 0.001;
        double c = calculate_c_ptr(r, f, s);
        double expected_c = 100000.0;
        
        
        std::vector<std::vector<double>> z(10, std::vector<double>(2, 0.0));
        for (int i = 0; i < 10; ++i) {
            z[i][0] = sin(i * 0.5);  
        }
        double delta_y = 100.0, delta_x = 1000.0;
        forward_space_step_ptr(z, c, delta_y, delta_x);
        
        
        double z_prev = z[4][0];
        double z_curr = z[5][0];
        double z_next = z[6][0];
        double laplacian_expected = (z_next - 2.0 * z_curr + z_prev) / (delta_x * delta_x);
        
        
        double dx2 = delta_x * delta_x;
        double coeff = c * delta_y / dx2;
        if (std::abs(coeff) > 1e6) {
            coeff = (coeff > 0) ? 1e6 : -1e6;
        }
        double z_expected = z_curr - coeff * laplacian_expected;
        
        test2_passed = (std::abs(c - expected_c) < 1e-12 &&
                       std::abs(z[5][1] - z_expected) < 1e-10 &&
                       std::isfinite(z[5][1]));
        if (test2_passed) {
            std::cout << "correct\n";
        } else {
            std::cout << "wrong\n";
        }
    }
    
    
    {
        double r = 0.0001, f = 0.0001, s = 0.001;
        double c = calculate_c_ptr(r, f, s);
        double expected_c = 1000.0;
        
        
        std::vector<std::vector<double>> z(20, std::vector<double>(2, 0.0));
        for (int i = 0; i < 20; ++i) {
            z[i][0] = exp(-(i-10)*(i-10)/10.0);  
        }
        double delta_y = 50.0, delta_x = 500.0;
        
        
        for (int step = 0; step < 10; ++step) {
            forward_space_step_ptr(z, c, delta_y, delta_x);
            
            for (size_t i = 0; i < z.size(); ++i) {
                z[i][0] = z[i][1];
            }
        }
        
        
        double sum_before = 0.0, sum_after = 0.0;
        for (int i = 0; i < 20; ++i) {
            sum_before += exp(-(i-10)*(i-10)/10.0);
            sum_after += z[i][0];
        }
        double mass_change = std::abs(sum_after - sum_before) / sum_before;
        
        test3_passed = (std::abs(c - expected_c) < 1e-12 &&
                       std::isfinite(z[10][0]) &&
                       mass_change < 0.1 &&
                       !std::isnan(z[10][0]) && !std::isinf(z[10][0]));
        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 write C++ code to implement a function named problem_4_02. This function defines two lambdas for vector initialization by threshold and 2D grid boundary condition application, then assigns the lambda addresses to the corresponding passed function pointer targets.
function_header: void problem_4_02() 
test_cases: int main() {
    // Function pointers in main
    void (*initialize_step_ptr)(std::vector<double>&, const std::vector<double>&, double) = nullptr;
    void (*apply_boundary_conditions_ptr)(std::vector<std::vector<double>>&, double, double, double, double, double) = nullptr;
    
    // Set up pointer-to-pointer so problem_4_02 can set our function pointers
    initialize_step_pptr = &initialize_step_ptr;
    apply_boundary_conditions_pptr = &apply_boundary_conditions_ptr;
    
    // Initialize problem function (it will set the function pointers)
    problem_4_02();

    std::cout << "========================================\n";
    std::cout << "Problem 4-Subproblem 2: Initial Conditions and Boundary Condition Handling\n";
    std::cout << "========================================\n";
    
    bool test1_passed = false, test2_passed = false, test3_passed = false;
    
    
    {
        std::vector<double> x;
        
        double base_dx = 4873.5;  
        for (int i = 0; i <= 150; ++i) {
            x.push_back(i * base_dx + 0.5 * sin(i * 0.1));  
        }
        std::vector<double> z0(151, 0.0);
        
        
        double threshold1 = 250000.0;
        double threshold2 = 500000.0;
        double threshold3 = 750000.0;
        
        
        for (size_t i = 0; i < x.size(); ++i) {
            if (x[i] <= threshold1 + 1e-10) {
                z0[i] = 15.0 + 2.5 * sin(x[i] / 10000.0);  
            } else if (x[i] <= threshold2 + 1e-10) {
                z0[i] = 8.0 + 1.5 * cos(x[i] / 15000.0);   
            } else if (x[i] <= threshold3 + 1e-10) {
                z0[i] = 3.0 + 0.8 * sin(x[i] / 20000.0);   
            } else {
                z0[i] = 0.0;  
            }
        }
        
        
        size_t idx1 = 51;   
        size_t idx2 = 102;  
        size_t idx3 = 153;  
        if (idx3 >= z0.size()) idx3 = z0.size() - 1;
        
        double z_at_threshold1 = z0[idx1];
        double z_at_threshold2 = z0[idx2];
        double z_at_threshold3 = z0[idx3];
        
        
        bool range1_ok = (z_at_threshold1 >= 12.5 && z_at_threshold1 <= 17.5);
        bool range2_ok = (z_at_threshold2 >= 6.5 && z_at_threshold2 <= 9.5);
        bool range3_ok = (z_at_threshold3 >= 0.0 && z_at_threshold3 <= 3.8);
        
        
        bool monotonic_ok = true;
        
        double avg_first = 0.0, avg_last = 0.0;
        size_t count_first = std::min(static_cast<size_t>(50), z0.size());
        size_t count_last = std::min(static_cast<size_t>(50), z0.size());
        for (size_t i = 0; i < count_first; ++i) {
            avg_first += z0[i];
        }
        for (size_t i = z0.size() - count_last; i < z0.size(); ++i) {
            avg_last += z0[i];
        }
        avg_first /= count_first;
        avg_last /= count_last;
        monotonic_ok = (avg_first > avg_last);  
        
        test1_passed = (range1_ok && range2_ok && range3_ok && monotonic_ok);
        if (test1_passed) {
            std::cout << "correct\n";
        } else {
            std::cout << "wrong\n";
        }
    }
    
    
    {
        std::vector<double> x;
        for (int i = 0; i <= 100; ++i) {
            x.push_back(i * 5000.0);
        }
        std::vector<double> z0(101);
        double decay_length = 10000.0;
        for (size_t i = 0; i < x.size(); ++i) {
            
            double arg = -x[i] / decay_length;
            if (arg < -700.0) arg = -700.0;  
            z0[i] = 10.0 * exp(arg);
        }
        
        
        double expected_z0 = 10.0 * exp(0.0);
        double expected_z50 = 10.0 * exp(-50.0 * 5000.0 / 10000.0);
        double expected_z100 = 10.0 * exp(-100.0 * 5000.0 / 10000.0);
        
        
        
        
        double decay_rate_0_50 = (z0[0] - z0[50]) / (x[50] - x[0]);
        double expected_decay_rate = 10.0 / decay_length;  
        
        test2_passed = (std::abs(z0[0] - expected_z0) < 1e-10 &&
                       std::abs(z0[50] - expected_z50) < 1e-10 &&
                       std::abs(z0[100] - expected_z100) < 1e-10 &&
                       decay_rate_0_50 > 0.0 &&
                       z0[0] > z0[50] && z0[50] > z0[100]);
        if (test2_passed) {
            std::cout << "correct\n";
        } else {
            std::cout << "wrong\n";
        }
    }
    
    
    {
        std::vector<std::vector<double>> z(101, std::vector<double>(500, 1.0));
        double F = 1.0, f = 0.0001, r = 0.001, g = 9.8, delta_x = 5000.0;
        
        
        for (size_t i = 1; i < 100; ++i) {
            z[i][499] = 1.0 + i * 0.01;  
        }
        
        apply_boundary_conditions_ptr(z, F, f, r, g, delta_x);
        
        
        double expected_left = z[1][499] - (F * f) / (r * g) * delta_x;
        double right_boundary = z[100][499];
        
        
        double gradient_left = (z[1][499] - z[0][499]) / delta_x;
        double expected_gradient = (F * f) / (r * g);
        
        test3_passed = (std::abs(z[0][499] - expected_left) < 1e-10 &&
                       std::abs(right_boundary) < 1e-12 &&
                       std::abs(gradient_left - expected_gradient) < 1e-10 &&
                       std::isfinite(z[0][499]));
        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++ code to generate a function named problem_4_03.
This function defines two lambdas: one calculates parameter c with numeric exception handling including overflow, underflow and nan/inf processing, the other executes 1D forward finite difference step calculation for 2D double vectors, then assigns lambdas to corresponding external function pointers.
function_header: void problem_4_03() 
test_cases: int main() {
    // Function pointers in main
    double (*calculate_c_ptr)(double, double, double) = nullptr;
    void (*forward_space_step_ptr)(std::vector<std::vector<double>>&, double, double, double) = nullptr;

    // Set up pointer-to-pointer so problem_4_03 can set our function pointers
    calculate_c_pptr = &calculate_c_ptr;
    forward_space_step_pptr = &forward_space_step_ptr;

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

    std::cout << "========================================\n";
    std::cout << "Problem 4-Subproblem 3: Numerical Stability Analysis and Verification\n";
    std::cout << "========================================\n";
    
    bool test1_passed = false, test2_passed = false, test3_passed = false;
    
    
    {
        std::vector<std::vector<double>> z(101, std::vector<double>(2, 0.0));
        for (int i = 0; i < 11; ++i) {
            z[i][0] = 10.0;
        }
        
        
        double energy_before = 0.0;
        for (size_t i = 0; i < z.size(); ++i) {
            energy_before += z[i][0] * z[i][0];
        }
        
        double c = calculate_c_ptr(0.001, 0.0001, 0.001);
        double delta_y = -1000.0, delta_x = 5000.0;
        forward_space_step_ptr(z, c, delta_y, delta_x);
        
        
        double energy_after = 0.0;
        for (size_t i = 0; i < z.size(); ++i) {
            energy_after += z[i][1] * z[i][1];
        }
        
        
        double z_prev = z[4][0];
        double z_curr = z[5][0];
        double z_next = z[6][0];
        double laplacian = (z_next - 2.0 * z_curr + z_prev) / (delta_x * delta_x);
        double expected_z5 = z_curr - c * delta_y * laplacian;
        
        test1_passed = (z.size() == 101 &&
                       std::isfinite(z[5][1]) &&
                       std::abs(z[5][1] - expected_z5) < 1e-10 &&
                       energy_after < energy_before &&
                       (energy_before - energy_after) / energy_before > 0.0);
        if (test1_passed) {
            std::cout << "correct\n";
        } else {
            std::cout << "wrong\n";
        }
    }
    
    
    {
        std::vector<std::vector<double>> z(101, std::vector<double>(4, 0.0));
        for (int i = 0; i < 11; ++i) {
            z[i][0] = 10.0;
        }
        double c = calculate_c_ptr(0.001, 0.0001, 0.001);
        
        
        std::vector<double> z5_history;
        z5_history.push_back(z[5][0]);
        
        for (int j = 0; j < 3; ++j) {
            forward_space_step_ptr(z, c, -1000.0, 5000.0);
            z5_history.push_back(z[5][1]);
            for (size_t i = 0; i < z.size(); ++i) {
                z[i][0] = z[i][1];
            }
        }
        
        
        bool monotonic = true;
        for (size_t i = 1; i < z5_history.size(); ++i) {
            if (z5_history[i] > z5_history[i-1] + 1e-10) {
                monotonic = false;
                break;
            }
        }
        
        
        double convergence_rate = (z5_history.size() > 1) ? 
            std::abs(z5_history[z5_history.size()-1] - z5_history[z5_history.size()-2]) / 
            (std::abs(z5_history[z5_history.size()-2]) + 1e-15) : 0.0;
        
        test2_passed = (z.size() == 101 &&
                       std::isfinite(z[5][0]) &&
                       z[5][0] >= 0.0 && z[5][0] <= 10.0 &&
                       monotonic &&
                       convergence_rate < 1.0);
        if (test2_passed) {
            std::cout << "correct\n";
        } else {
            std::cout << "wrong\n";
        }
    }
    
    
    {
        std::vector<std::vector<double>> z(151, std::vector<double>(6, 0.0));
        for (int i = 0; i < 16; ++i) {
            z[i][0] = 10.0;
        }
        double c = calculate_c_ptr(0.001, 0.0001, 0.001);
        
        
        std::vector<double> z8_values;
        z8_values.push_back(z[8][0]);
        
        for (int j = 0; j < 5; ++j) {
            forward_space_step_ptr(z, c, -1000.0, 5000.0);
            z8_values.push_back(z[8][1]);
            for (size_t i = 0; i < z.size(); ++i) {
                z[i][0] = z[i][1];
            }
        }
        
        
        double diffusion_length = sqrt(2.0 * std::abs(c * 5.0 * (-1000.0)));  
        double initial_width = 10.0 * 5000.0;  
        double spread_ratio = diffusion_length / initial_width;
        
        
        bool asymptotic_ok = true;
        for (size_t i = 1; i < z8_values.size(); ++i) {
            if (z8_values[i] > z8_values[i-1] + 1e-8) {  
                asymptotic_ok = false;
                break;
            }
        }
        
        test3_passed = (z.size() == 151 &&
                       std::isfinite(z[8][0]) &&
                       z[8][0] >= 0.0 && z[8][0] <= 10.0 &&
                       asymptotic_ok &&
                       spread_ratio > 0.1);
        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: