对于不同的微分矩阵,热方程计算变得不稳定

时间:2018-02-05 13:43:59

标签: c++ matrix runge-kutta

我想使用RK4算法在一个维度上求解热方程。现在我可以将热方程写为

d_t u = d_x(A d_x u)

d_t u = A d_x^2(u)

A一个常数。 我想比较两种方法,因此编写了以下代码:

#include <iostream>
#include <math.h>
#include <armadillo>

#define Y0 0
#define Y1 1
#define YSIZE 400
#define ALPHA 1

auto rk4(double f(double, double))
{
        return
        [       f            ](double t, double y, double dt ) -> double{ return
        [t,y,dt,f            ](                    double dy1) -> double{ return
        [t,y,dt,f,dy1        ](                    double dy2) -> double{ return
        [t,y,dt,f,dy1,dy2    ](                    double dy3) -> double{ return
        [t,y,dt,f,dy1,dy2,dy3](                    double dy4) -> double{ return
        ( dy1 + 2*dy2 + 2*dy3 + dy4 ) / 6   ;} (
        dt * f( t+dt  , y+dy3   )          );} (
        dt * f( t+dt/2, y+dy2/2 )          );} (
        dt * f( t+dt/2, y+dy1/2 )          );} (
        dt * f( t     , y       )          );} ;
}

auto rk4(arma::colvec f(double, arma::colvec))
{
        return
        [       f            ](double t, arma::colvec y, double dt ) -> arma::colvec{ return
        [t,y,dt,f            ](                    arma::colvec dy1) -> arma::colvec{ return
        [t,y,dt,f,dy1        ](                    arma::colvec dy2) -> arma::colvec{ return
        [t,y,dt,f,dy1,dy2    ](                    arma::colvec dy3) -> arma::colvec{ return
        [t,y,dt,f,dy1,dy2,dy3](                    arma::colvec dy4) -> arma::colvec{ return
        ( dy1 + 2*dy2 + 2*dy3 + dy4 ) / 6   ;} (
        dt * f( t+dt  , y+dy3   )          );} (
        dt * f( t+dt/2, y+dy2/2 )          );} (
        dt * f( t+dt/2, y+dy1/2 )          );} (
        dt * f( t     , y       )          );} ;
}

void create_first_derivative_matrix(const size_t size, arma::mat &matrix, double dh = -1)
{
    if(dh <= 0)
        dh = ((double)Y1 - (double)Y0) / ((double)YSIZE - 1.);
    matrix = arma::mat(size, size, arma::fill::zeros);
    matrix.diag(1) = arma::colvec(size - 1, arma::fill::ones) * 8;
    matrix.diag(-1) = arma::colvec(size - 1, arma::fill::ones) * -8;
    matrix.diag(2) = arma::colvec(size - 2, arma::fill::ones) * -1;
    matrix.diag(-2) = arma::colvec(size - 2, arma::fill::ones);

#ifdef USE_REFLECT
    matrix(0, 0) = 0;
    matrix(0, 1) = 0;
    matrix(0, 2) = 0;
    matrix(0, 3) = 0;
    matrix(0, 4) = 0;
    matrix(1, 0) = -8;
    matrix(1, 1) = 1;
    matrix(1, 2) = 8;
    matrix(1, 3) = -1;
    matrix(1, 4) = 0;
    matrix(size - 1, size - 1) = 0;
    matrix(size - 1, size - 2) = 0;
    matrix(size - 1, size - 3) = 0;
    matrix(size - 1, size - 4) = 0;
    matrix(size - 1, size - 5) = 0;
    matrix(size - 2, size - 1) = 8;
    matrix(size - 2, size - 2) = -1;
    matrix(size - 2, size - 3) = -8;
    matrix(size - 2, size - 4) = 1;
    matrix(size - 2, size - 5) = 0;
#else
    matrix(0, 0) = -25;
    matrix(0, 1) = 48;
    matrix(0, 2) = -36;
    matrix(0, 3) = 16;
    matrix(0, 4) = -3;
    matrix(1, 0) = -3;
    matrix(1, 1) = -10;
    matrix(1, 2) = 18;
    matrix(1, 3) = -6;
    matrix(1, 4) = 1;
    matrix(size - 1, size - 1) = 25;
    matrix(size - 1, size - 2) = -48;
    matrix(size - 1, size - 3) = 36;
    matrix(size - 1, size - 4) = -16;
    matrix(size - 1, size - 5) = 3;
    matrix(size - 2, size - 1) = 3;
    matrix(size - 2, size - 2) = 10;
    matrix(size - 2, size - 3) = -18;
    matrix(size - 2, size - 4) = 6;
    matrix(size - 2, size - 5) = -1;
#endif
    matrix = matrix / (12 * dh);
}

void create_second_derivative_matrix(const size_t size, arma::mat &matrix, double dh = -1)
{
    if(dh <= 0)
        dh = ((double)Y1 - (double)Y0) / ((double)YSIZE - 1.);
    matrix = arma::mat(size, size, arma::fill::zeros);
    matrix.diag(1) = arma::colvec(size - 1, arma::fill::ones) * 16;
    matrix.diag(-1) = arma::colvec(size - 1, arma::fill::ones) * 16;
    matrix.diag(2) = arma::colvec(size - 2, arma::fill::ones) * -1;
    matrix.diag(-2) = arma::colvec(size - 2, arma::fill::ones) * -1;
    matrix.diag(0) = arma::colvec(size, arma::fill::ones) * -30;
    matrix(0, 0) = 35;
    matrix(0, 1) = -104;
    matrix(0, 2) = 114;
    matrix(0, 3) = -56;
    matrix(0, 4) = 11;
    matrix(1, 0) = 11;
    matrix(1, 1) = -20;
    matrix(1, 2) = 6;
    matrix(1, 3) = 4;
    matrix(1, 4) = -1;
    matrix(size - 1, size - 1) = 35;
    matrix(size - 1, size - 2) = -104;
    matrix(size - 1, size - 3) = 114;
    matrix(size - 1, size - 4) = -56;
    matrix(size - 1, size - 5) = 11;
    matrix(size - 2, size - 1) = 11;
    matrix(size - 2, size - 2) = -20;
    matrix(size - 2, size - 3) = 6;
    matrix(size - 2, size - 4) = 4;
    matrix(size - 2, size - 5) = -1;
    matrix = matrix / (12 * dh * dh);
}

arma::colvec eval_heat_eqn(const double t, const arma::colvec y)
{
    (void) t;
    arma::mat deriv_matrix;
#define USE_FIRST_ORDER
#ifdef USE_FIRST_ORDER
    create_first_derivative_matrix(y.size(), deriv_matrix);
#else
    create_second_derivative_matrix(y.size(), deriv_matrix);
#endif
    arma::colvec heat_vec = arma::colvec(y.size(), arma::fill::zeros);
    arma::colvec alpha_vec = arma::colvec(y.size(), arma::fill::ones) * ALPHA;

    arma::mat alpha_mat = arma::mat(y.size(), y.size(), arma::fill::zeros);
    alpha_mat.diag() = alpha_vec;
    for(size_t i = 0; i < y.size(); ++i)
        if(i >= 0.5 * y.size())
            heat_vec(i) = 0.01;
        else
            heat_vec(i) = 0;
#ifdef USE_FIRST_ORDER
    return alpha_mat * ((deriv_matrix * deriv_matrix) * y) + heat_vec;
#else
    return alpha_mat * (deriv_matrix * y) + heat_vec;
#endif
}

//Prints a vector
void print_vector(const arma::colvec &x, const arma::colvec &vector_data, std::string filename)
{
    std::ofstream out(filename.c_str());
    for(size_t i = 0; i < vector_data.size(); ++i)
        out << x(i) << '\t' << abs(vector_data[i]) << '\n';
    out.close();
}

int main(void)
{
        const double TIME_MAXIMUM = 10, WHOLE_TOLERANCE = 1e-12 ;
        const double T_START = 0.0;
        double DT = 0.00010;
        arma::mat local_deriv_matrix_I, local_deriv_matrix_II;

        auto is_whole      = [WHOLE_TOLERANCE](double t          )->bool  { return fabs(t-round(t)) < WHOLE_TOLERANCE; } ;

        double t = T_START ;
        const double stability_value = ALPHA /(pow(((double)Y1 - (double)Y0) / ((double)YSIZE + 1.), 2));
        DT = 0.15 / stability_value;
        std::cout << "Stability: " << ALPHA * DT/(pow(((double)Y1 - (double)Y0) / ((double)YSIZE + 1.), 2)) << '\n';
        std::cout << "DT: " << DT << '\n';


        auto dy_vec = rk4( eval_heat_eqn ) ;

        arma::colvec y_vec = arma::colvec(YSIZE, arma::fill::ones);//
        arma::colvec test_vec = arma::linspace(Y0, Y1, YSIZE);
        arma::colvec x_vec = arma::linspace(Y0, Y1, y_vec.size());
        test_vec = arma::pow(test_vec, 3);
        create_first_derivative_matrix(y_vec.size(), local_deriv_matrix_I);
        create_second_derivative_matrix(y_vec.size(), local_deriv_matrix_II);

        arma::colvec deriv_vec = local_deriv_matrix_I * test_vec;
        print_vector(x_vec, test_vec, "test_vector.txt");
        print_vector(x_vec, deriv_vec, "derivation_vector.txt");
        deriv_vec = local_deriv_matrix_I * local_deriv_matrix_I * test_vec;
        print_vector(x_vec, deriv_vec, "derivation_II_vector.txt");
        deriv_vec = local_deriv_matrix_II * test_vec;
        print_vector(x_vec, deriv_vec, "II_derivation_vector.txt");

        t = T_START ;
        std::vector<arma::colvec> results;

        while(t <= TIME_MAXIMUM) {
          if (is_whole(t)) { printf("y(%4.2f)[0]\t=(%12.6f)\n", t, y_vec[0]); }
          y_vec += dy_vec(t,y_vec,DT) ; t += DT; results.push_back(y_vec);
        }

        arma::mat result_matrix = arma::mat(y_vec.size(), results.size(), arma::fill::zeros);
        for(size_t i = 0; i < results.size(); ++i)
            if(i % 100 == 0)
                result_matrix.col(i) = results[i];
        result_matrix.save("heat_results.txt", arma::raw_ascii);
        return 0;
}

现在,在为#define USE_FIRST_ORDER运行此程序时(即使用第一种方法),我的结果看起来像enter image description here 而当直接使用二阶矩阵时,我得到另一个结果(与第一个版本相比,这是正确的): enter image description here 为了进行调试,我将矩阵M1(第一顺序)和M2(第二顺序)与向量相乘时进行了比较:

M1*M1*y=M2*y

双方获得相同的结果。因此,我认为我在其他地方遇到了问题,但我找不到它。

0 个答案:

没有答案