现在的位置: 首页 > 综合 > 正文

用 GSL 解常微分方程初值问题

2013年10月07日 ⁄ 综合 ⁄ 共 4035字 ⁄ 字号 评论关闭

用 GSL 解常微分方程初值问题

GNU Scientific Library (GSL) 是一个用于科学计算的 C 语言类库。这里介绍如何用 GSL 来进行常微分方程初值问题的计算。

n 维的常微分方程组一般可以描述为:
\[
\frac{{dy_i (t)}}{{dx}} = f_i (t,y_1 (t), \ldots y_n (t))
\]
其中 i = 1,\ldots,n
相应的初值条件为 y_i (t_0) = Y_{i0}

GSL 提供了许多的方法用来解决常微分方程的初值问题,包括底层的方法如 Runge-Kutta 法和 Bulirsch-Stoer 法,也包括高层的算法如自适应步长控制等。

GSL 中用一个结构体 gsl_odeiv_system 来描述常微分方程组,

  1. typedef struct    
  2. {  
  3.   int (* function) (double t, const double y[], double dydt[], void * params);  
  4.   int (* jacobian) (double t, const double y[], double * dfdy, double dfdt[], void * params);  
  5.   size_t dimension;  
  6.   void * params;  
  7. }  
  8. gsl_odeiv_system;  

function 是一个函数指针,指向用户定义的函数,t, y[], params作为输入参数,dydt 返回 n 维的向量场 f_i(t, y, params),计算成功时,函数应该返回 GSL_SUCCESS;

jacobian 也是一个函数指针,指向用户定义的函数,t, y[], params作为输入参数,dfdt 返回 \partial f_i /\partial t,dfdy 返回 Jacobian 矩阵 J_{ij}, 存储方式为J(i,j) = dfdy[i * dimension + j],计算成功时,函数应该返回 GSL_SUCCESS;

对于一些简单的算法并不需要 Jacobian 矩阵,此时可以将 jacobian 指向空(NULL).但是大多数高效的算法都要用到 Jacobian 矩阵,因此在能提供 Jacobian 矩阵时应该尽量的提供。

dimension 描述系统的维数。

params 为一个任意的指针,具体的作用在下面的例子中讲解。

下面分为三个部分介绍 GSL 中提供的函数。

1 步进函数

步进函数是用户可以调用的最底层的功能函数,其他的高层函数也都是通过调用步进函数来完成最后的计算工作的。

用来分配和释放所需空间

gsl_odeiv_step * gsl_odeiv_step_alloc(const gsl_odeiv_step_type * T, size_t dim);
int  gsl_odeiv_step_reset(gsl_odeiv_step * s);
void gsl_odeiv_step_free(gsl_odeiv_step * s);

其中 gsl_odeiv_step_type 可以为以下值:

  1. gsl_odeiv_step_rk2;    //二阶 Runge-Kutta 法  
  2. gsl_odeiv_step_rk4;    //四阶经典 Runge-Kutta 法  
  3. gsl_odeiv_step_rkf45;  //Runge-Kutta-Fehlberg 法  
  4. gsl_odeiv_step_rkck;  //Runge-Kutta Cash-Karp  
  5. gsl_odeiv_step_rk8pd; //Runge-Kutta Prince-Dormand  
  6. gsl_odeiv_step_rk2imp; //隐式的 Runge-Kutta   
  7. gsl_odeiv_step_rk2simp;  
  8. gsl_odeiv_step_rk4imp; //Implicit 4th order Runge-Kutta at Gaussian points  
  9. gsl_odeiv_step_bsimp; //隐式的 Bulirsch-Stoer method of Bader and Deuflha (需要 Jacobian 矩阵)  
  10. gsl_odeiv_step_gear1; //M=1 implicit Gear method  
  11. gsl_odeiv_step_gear2; //M=2 implicit Gear method  

返回一些所用算法的信息

  1. const char * gsl_odeiv_step_name(const gsl_odeiv_step *);  
  2. unsigned int gsl_odeiv_step_order(const gsl_odeiv_step * s);  

具体的步进计算函数如下

  1. int  gsl_odeiv_step_apply(gsl_odeiv_step *s, double t, double h, double y[], double yerr[], const double dydt_in[], double dydt_out[], const gsl_odeiv_system * dydt);  

计算 t -> t +h, t + h 时刻的值存储在 y[] 中,yerr[] 是对 y[] 计算误差的估计值。如果 dydt_in[] 不为 null ,dydt_in[] 应为 t 时刻的对时间的导数,如果没有提供 dydt_in[] 的话,这个函数的内部会作相应的计算。dydt_out[] 输出t + h 时刻对时间的导数。

下面是一个简单的例子:
考虑二阶的非线性Van der Pol 方程,
\[
x''(t) + \mu x'(t)(x(t)^2  - 1) + x(t) = 0
\]
这个方程可以被化简为如下的常微分方程组:

y_1' = -y_0 + \mu y_1 (1 - {y_0}^2)
y_0' = y_1

Jacobian 矩阵为:
\[
J = \left( {\begin{array}{*{20}c}
   0 & 1  \\
   { - 1 - 2\mu y_0 y_1} & { \mu (1-{y_0}^2) }  \\
\end{array}} \right)
\]

  1. #include <stdio.h>  
  2. #include <gsl/gsl_errno.h>  
  3. #include <gsl/gsl_matrix.h>  
  4. #include <gsl/gsl_odeiv.h>  
  5.   
  6. int func (double t, const double y[], double f[], void *params)  
  7. {// 定义微分方程组  
  8.  double mu = *(double *)params;  
  9.  f[0] = y[1];  
  10.  f[1] = -y[0] - mu*y[1]*(y[0]*y[0] - 1);  
  11.  return GSL_SUCCESS;  
  12. }  
  13.   
  14. int jac (double t, const double y[], double *dfdy, double dfdt[], void *params)  
  15. {  
  16.  double mu = *(double *)params;  
  17.  gsl_matrix_view dfdy_mat = gsl_matrix_view_array (dfdy, 2, 2);  
  18.  gsl_matrix * m = &dfdy_mat.matrix;  
  19.  gsl_matrix_set (m, 0, 0, 0.0);  
  20.  gsl_matrix_set (m, 0, 1, 1.0);  
  21.  gsl_matrix_set (m, 1, 0, -2.0*mu*y[0]*y[1] - 1.0);  
  22.  gsl_matrix_set (m, 1, 1, -mu*(y[0]*y[0] - 1.0));  
  23.  dfdt[0] = 0.0;  
  24.  dfdt[1] = 0.0;  
  25.  return GSL_SUCCESS;  
  26. }  
  27.   
  28. int main (void)  
  29. {  
  30.  const gsl_odeiv_step_type * T = gsl_odeiv_step_rk8pd;  
  31.  gsl_odeiv_step * s = gsl_odeiv_step_alloc (T, 2);  
  32.   
  33.  double mu = 10;  
  34.  gsl_odeiv_system sys = {func, jac, 2, &mu};  
  35.  double t = 0.0, t1 = 100.0;  
  36.  double h = 1e-6; // 步进为 h  
  37.  double y[2] = { 1.0, 0.0 }; //初值  
  38.  double y_err[2];  
  39.  double dydt_in[2], dydt_out[2];  
  40.    
  41.  GSL_ODEIV_FN_EVAL(&sys, t, y, dydt_in); //计算初始时刻的dy/dt  
  42.  while (t < t1)  
  43.  {  
  44.   int status = gsl_odeiv_step_apply (  
  45.   s, t, h, y, y_err, dydt_in, dydt_out, &sys);  
  46.   if (status != GSL_SUCCESS) break;  
  47.   dydt_in[0] = dydt_out[0];  
  48.   dydt_in[1] = dydt_out[1];  
  49.   t += h;  
  50.   printf ("%.5e %.5e %.5e\n", t, y[0], y[1]);  
  51.  }  
  52.  gsl_odeiv_step_free (s);  
  53.  return 0;  

抱歉!评论已关闭.