【问题标题】:Numerical differential equation solver algorithm segfaults unexpectedly数值微分方程求解器算法意外出现段错误
【发布时间】:2013-05-12 20:11:09
【问题描述】:

我试图用倍频程求解一个微分方程,但我选择的微分单位需要很长时间,所以我决定用 C 对其进行编码。算法如下:

#include <stdio.h>

double J = 5.78e-5; // (N.m)/(rad/s^2)
double bo = 6.75e-4; // (N.m)/(rad/s)
double ko = 5.95e-4; // (N.m)/rad
double Ka = 1.45e-3; // (N.m)/A
double Kb = 1.69e-3; // V/(rad/s)
double L = 0.311e-3; // mH
double R = 150; // ohms
double E = 5; // V

// Simulacion
int tf = 2;
double h = 1e-6;

double dzdt, dwdt, didt;

void solver(double t, double z, double w, double i) {
    printf("%f %f %f\n", z, w, i);
    if (t >= tf) {
        printf("Finished!\n");
        return; // End simulation
    }
    else {
        dzdt = w;
        dwdt = 1/J*( Ka*i - ko*z - bo*w );
        didt = 1/L*( E - R*i - Kb*w );
        // Solve next step with newly calculated "initial conditions"
        solver(t+h, z+h*dzdt, w+h*dwdt, i+h*didt);
    }
}

int main() {
    solver(0, 0, 0, 0);
    // Solve data
    // Write data to file
    return 0;
}

定义为h(如您所见)的微分单位必须那么小,否则值会失控并且解决方案将不正确。现在,h 的数值更大,程序从头到尾都没有错误(nan 值除外),但是我选择了h,我得到了分段错误;是什么原因造成的?

替代 Octave 解决方案

在我的一个朋友告诉我他能够使用 MATLAB 使用 1e-3 的微分步长求解方程后,我发现 MATLAB 有一个“僵硬”版本的 ode23 模块——“僵硬”对于求解那些需要极小步长的微分方程有特殊意义。后来我在 Octave 中搜索了“僵硬”的 ODE 求解器,发现 lsode 属于该类别。在第一次尝试中,lsode 在微秒内解决了方程(都比 MATLAB 和我的 C 实现快),并且得到了完美的解决方案。 FOSS 万岁!

【问题讨论】:

    标签: c math segmentation-fault numerical-methods differential-equations


    【解决方案1】:

    求解器函数递归调用自身。有多深?大概是几百万次。你的堆栈空间用完了吗?每个递归都需要四个双精度数和一个堆栈帧的空间,这很快就会加起来。

    我建议你将求解器重写为迭代而不是递归函数。

    【讨论】:

      【解决方案2】:

      你的递归没有足够快地终止,所以你正在耗尽你的堆栈。

      要解决这个问题,只需让它成为一个循环,看起来你实际上并没有在做任何需要递归的事情。

      我认为这样做:

      void solver(double t, double z, double w, double i) {
          while (!(t >= tf)) {
              printf("%f %f %f\n", z, w, i);
              dzdt = w;
              dwdt = 1/J*( Ka*i - ko*z - bo*w );
              didt = 1/L*( E - R*i - Kb*w );
              // Solve next step with newly calculated "initial conditions"
              t = t+h;
              z = z+h*dzdt;
              w = w+h*dwdt;
              i = i+h*didt;
          } 
          printf("Finished!\n");
      }
      

      附带说明一下,您的函数有资格进行尾递归优化,因此,如果您在打开一些优化的情况下编译它(例如 -O2),任何体面的编译器实际上都足够聪明,可以进行尾递归调用,并且您的程序不会出现段错误。

      【讨论】:

        【解决方案3】:

        您正在对求解器进行 1e6 次递归调用。我猜你的堆栈用完了。尝试在求解器中使用循环更新变量而不是调用函数。

        伪代码:

         while t < tf
              do dt step
              t = t + dt
        

        等等

        【讨论】:

          【解决方案4】:

          正如@hexist 所说,这里不需要递归。

          堆栈溢出:

          ==4734== Memcheck, a memory error detector
          ==4734== Copyright (C) 2002-2011, and GNU GPL'd, by Julian Seward et al.
          ==4734== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyright info
          ==4734== Command: ./demo
          ==4734== 
          ==4734== Stack overflow in thread 1: can't grow stack to 0x7fe801ff8
          ==4734== 
          ==4734== Process terminating with default action of signal 11 (SIGSEGV)
          ==4734==  Access not within mapped region at address 0x7FE801FF8
          ==4734==    at 0x40054E: solver (demo.c:18)
          ==4734==  If you believe this happened as a result of a stack
          ==4734==  overflow in your program's main thread (unlikely but
          ==4734==  possible), you can try to increase the size of the
          ==4734==  main thread stack using the --main-stacksize= flag.
          ==4734==  The main thread stack size used in this run was 8388608.
          ==4734== Stack overflow in thread 1: can't grow stack to 0x7fe801fe8
          ==4734== 
          ==4734== Process terminating with default action of signal 11 (SIGSEGV)
          ==4734==  Access not within mapped region at address 0x7FE801FE8
          ==4734==    at 0x4A226E0: _vgnU_freeres (vg_preloaded.c:58)
          ==4734==  If you believe this happened as a result of a stack
          ==4734==  overflow in your program's main thread (unlikely but
          ==4734==  possible), you can try to increase the size of the
          ==4734==  main thread stack using the --main-stacksize= flag.
          ==4734==  The main thread stack size used in this run was 8388608.
          ==4734== 
          ==4734== HEAP SUMMARY:
          ==4734==     in use at exit: 0 bytes in 0 blocks
          ==4734==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
          ==4734== 
          ==4734== All heap blocks were freed -- no leaks are possible
          ==4734== 
          ==4734== For counts of detected and suppressed errors, rerun with: -v
          ==4734== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 4 from 4)
          

          【讨论】:

            【解决方案5】:

            嗯,你已经得到了很多关于你的实际问题的答案。我只是想提请您注意另一个库。使用boost.odeint,如果您需要一个快速且易于使用的 ode 求解器,这基本上是最先进的库。忘掉 GSL、Matlab 等,Odeint 的表现都胜过所有这些。

            您的程序将如下所示:

            #include <boost/numeric/odeint.hpp>
            using namespace boost::numeric::odeint;
            
            typedef boost::array<double,3> State;
            
            const double J  = 5.78e-5; // (N.m)/(rad/s^2)
            const double bo = 6.75e-4; // (N.m)/(rad/s)
            const double ko = 5.95e-4; // (N.m)/rad
            const double Ka = 1.45e-3; // (N.m)/A
            const double Kb = 1.69e-3; // V/(rad/s)
            const double L  = 0.311e-3; // mH
            const double R  = 150; // ohms
            const double E  = 5; // V
            
            void my_ode( State const &s , State &dsdt , double t ) {
               double const &z = s[0],    // this is just a name
                            &w = s[1],    // forwarding for better
                            &i = s[2];    // readability of the ode
               dsdt[0] = w;
               dsdt[1] = 1./J * ( Ka*i - ko*z - bo*w );
               dsdt[2] = 1./L * ( E - R*i - Kb*w );
            }
            
            void printer( State const &s , double t ) {
               std::cout << s[0] << " " << s[1] << " " << s[2] << std::endl;
            }
            
            int main() {
               State s = {{ 0, 0, 0 }};
               integrate_const(
                  euler<State>() , my_ode , s , 0. , 2. , 1e-6 , printer
               );
            }
            

            【讨论】:

            • 嗯,也许我可能会在以后的活动中尝试这个,但实际上我只是决定用 C 编写这段代码,因为 Octave 在计算数值解时需要很长时间。我使用建议的解决方案编写的实现确实很不错:)
            猜你喜欢
            • 2014-11-03
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-03-16
            • 2018-12-22
            • 2021-11-17
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多