song.yz@foxmail.com wechat: math-box

计算数学



常微分方程数值解法



保辛积分

力学系统的哈密顿方程可以通过对力学系统的拉格朗日函数进行勒让德变换得到, 更简便导出正则方程的方法是由相空间的变分原理直接得到。
积分曲线pdqHdt当积分路路径端点恒在n维子空间 (t=t0,q=q0)上变动时,必定以γ为驻定曲线。 曲线γ是形式(t=t0,q=q0)的一条涡线。因此(t=t0,q=q0) 在“经过涡方向的无穷小平行四边形”上积分为零。
n个自由度力学系统的哈密顿方程如下 {dqdt=Hpdpdt=Hq 引入2n* 2n矩阵 J=[0InIn0] 则哈密顿方程可以写为 v=J(Hv) 其中 v=(qT,pT)T 在状态向量的形式下,定常正则变换可以用2n维向量的变换描述 ς=ς(v),ς=ς(QT,PT)T 其逆变换也是定常的,把逆变换和变换分别带入正则方程得到 ς˙=(ςv)TJ(ςv)Kv 定义 S=(ςv)T 正则变换要求变换后仍然保持正则形式,于是有正则变换的条件 STJS=J 满足这一条件的映射称为辛映射,S称为辛矩阵。 辛映射(或辛矩阵)有如下性质:
[封闭性] 辛映射的复合依然是辛映射。即a与b是任意的辛映射, 则有唯一确定的辛映射ab=c也是辛映射。
[满足结合律] 即对于辛映射a,b,c,满足(ab)c=a(bc)
[有单位元素存在] 存在单位映射1,有1a=a=a1。实际上即不进行正则变换,或者说变换就是其本身。
[辛映射存在逆映射] 即对于映射f,总存在逆映射a1使得a1a=1=aa1
因此辛映射(或辛矩阵)构成一个群,且有子群,如行列式为1的辛矩阵构成子群。 偶次微分流形的辛结构(symplectic struture中文有人译为辛构造)是一个闭的非退化的微分2形式 ω2,在流形M2n={(pT,qT)T}中, ω2可以表示为 ω2=dpdq 在辛流形(M2n,ω)上,哈密顿函数H的矢量场 JdH给出一个单参数微分同胚群gt:M2nM2nddt|t=0gt[qp]=JdH([qp])gt称为具有哈密顿函数H的哈密顿相流。 哈密顿相流具保持辛结构特性,即 (gt)ω2=ω2n=1时候M2n=R2,就是大家熟悉的相流保面积,亦即刘维尔定理。 线性哈密顿系统和可分哈密顿系统都是特殊的哈密顿系统, 可以从一般哈密顿系统的辛格式得到线性哈密顿系统和可分哈密顿系统的辛格式,也可以 针对线性哈密顿系统和可分哈密顿系统的特殊性寻求更为有效的辛格式。 如果哈密顿函数可以表示为 H(z)=H(q,p)=U(p)+V(p) 则称哈密顿系统是可分的,可分哈密顿系统的正则方程为 {dqdt=U(p)pdpdt=V(q)q 这里给出一种由日本学者Yoshida构造的4阶显式辛格式 x1=pn+c1h(V(qn)q),y1=qn+d1hU(x1)p x2=x1+c2h(V(y1)q),y2=y1+d2hU(x2)p x3=x2+c3h(V(y2)q),y3=y2+d2hU(x3)p pn+1=x3+c4h(V(y3)q),qn+1=y3+d4hU(pn+1)p 其中系数可以取 (c1c2c3c4)=(0αβα),(d1d2d3d4)=(α2α+β2α+β2α2) 或者取 (c1c2c3c4)=(α2α+β2α+β2α2),(d1d2d3d4)=(αβα0) 这里α=(2213)1,β=12α。 辛算法在长时间、多步数的计算中和保持系统整体结构上较传统的非辛算法显示出了 强大的优越性。目前,辛算法在天体力学的演化问题,分子动力学和量子物理等问题上取得了较多的研究成果。 本节以一个简单的范例对辛算法与普通的非辛算法进行积分比较,如果欲深入系统的了解 其他的辛差分格式请参考冯康、秦孟兆所著的《哈密顿系统的辛几何算法》。 谐振子是许多学科中最基本的问题,如量子力学、声学与振动等。 对于理想谐振子系统的拉格朗日函数为 L=TV=12mx˙12kx2 广义动量 p=Lx˙=mx˙ 哈密顿函数为 H=px˙L=1mp2+12kx2 由此可以建立哈密顿正则方程 {dxdt=pmdpdt=kx 这里以谐振子的长期演化来检验辛算法。代码为C#版本,代码用到C#定义的一些数学类,其定义方法见《C#科学计算讲义》。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
class cHamilton
{
    /*--------------------------------------class comment
     Version   :  V1.0
     Coded by  :  syz
     Date      :  2011-06-28 16:19:05          *星期二*
    ----------------------------------------------------
    parameters :
     *  k--弹簧劲度系数
     *  m--谐振子质量
     * 
    Methods    :
     *   Hamilton ----辛格式计算谐振子问题
     *   RK4----------4阶RK方法解算谐振子
     *   func---------谐振子的正则方程(哈密顿方程)
    --------------------------------------------------*/
    public double k = 1.0;
    //弹簧劲度系数,无量纲化
    public double m = 1.0;
    //振子质量,无量纲化
    public void Hamilton(long NN, double t0, double p0, double q0, double h)
    {
        StreamWriter ob1 = new StreamWriter("fout.txt");
      //  ob1.WriteLine("辛RK方法");
        double afa = 1.0 / (2.0 - Math.Pow(2.0, 1.0 / 3));
        double beta = 1 - 2.0 * afa;
        double c1 = 0.0;
        double c2 = afa;
        double c3 = beta;
        double c4 = afa;
        double d1 = afa / 2.0;
        double d2 = (afa + beta) / 2.0;
        double d3 = d2;
        double d4 = d1;
        double x1, x2, x3, y1, y2, y3, p, q, p1, q1;
        //积分初值
        p = p0;
        q = q0;
        for (int i = 0; i < NN; i++)
        {
            x1 = p - c1 * h * k*q;
            y1 = q + d1 * h * p / m;
            x2 = x1 - c2 * h * k * y1;
            y2 = y1 + d2 * h * x2 / m;
            x3 = x2 - c3 * h * k * y2;
            y3 = y2 + d3 * h * x3 / m;
            p1 = x3 - c4 * h * k * y3;
            q1 = y3 + d4 * h * p1 / m;
            p = p1;
            q = q1;
            //输出广义坐标与广义动量
                ob1.WriteLine("{0:F8}          {1:F8}", p, q);        
        }
        ob1.Close();
    }
    public void RK4(int N, int M, double t0, double h, double[] y0)
    {
     /* ----------------------------------------------------
    Desciption :

    parameters :
     * N----------积分步数
     * t0----积分初始时刻
     * h-----积分步长 
     * y0[M]------积分初值
     * M----------方程维数
    Methods    :
     *   solve----RK4方法函数
     *   func-----求解的方程函数
     * 
    --------------------------------------------------*/
        //生成输出文件
        StreamWriter ob2 = new StreamWriter("fRK.txt");
       // ob2.WriteLine("RK4方法解微分方程组");
        int i;
        //积分的第i步
        double[] k1 = new double[M];
        double[] k2 = new double[M];
        double[] k3 = new double[M];
        double[] k4 = new double[M];
        //注意这里不能再初始化,否则y0就会被重新设置为0
        //  y0=new double[M];
        //初值向量
        //计算结果,用文件输出
        double[] y = new double[M];
      //  double h, t;
      //  h = (tt - t0) / N;
        //步长
        //积分初值
        y = y0;
        double t = t0;
        double[] vec1 = new double[M];
        double[] vec2 = new double[M];
        double[] vec3 = new double[M];
        int j;  //分量指标
        for (i = 0; i < N; i++)
        {
            //第一次调用函数
            func(out k1, y, t);
            for (j = 0; j < M; j++)
                vec1[j] = y[j] + 0.5 * h * k1[j];
            //第二次调用函数
            func(out k2, vec1, t + 0.5 * h);
            for (j = 0; j < M; j++)
                vec2[j] = y[j] + 0.5 * h * k2[j];
            //第三次调用函数
            func(out k3, vec2, t + 0.5 * h);
            for (j = 0; j < M; j++)
                vec3[j] = y[j] + h * k3[j];
            //第四次调用函数
            func(out k4, vec3, t + h);
            for (j = 0; j < M; j++)
                y[j] = y[j] + (k1[j] + k2[j] * 2 + k3[j] * 2 + k4[j]) * h / 6.0;
            t = t + h;
            //输出计算结果
            ob2.WriteLine("{0:F8}    {1:F8}", y[0], y[1]);
        }
        ob2.Close();
        //关闭文件
    }
    public void func(out double[] f, double[] y, double t)
    {
        //右函数
        //y应变量
        //t自变量
        //f方程函数
        //不能对y重新执行以下语句,否则参数传递进来的y将被置于0
        //y = new double[2];
        f = new double[2];
        f[0] = y[1]/m;
        f[1] = -k*y[0];     
    }
}
namespace Hamilton
{
    class Program
    {
        static void Main(string[] args)
        {
            cHamilton ob;
            ob=new cHamilton();
            //用辛算法解谐振子问题
            ob.Hamilton(100000,0.0, 20.0, 8.0, 0.5);
            //Hamilton(long NN, double t0,double p0,double q0, double h)
            double[] y0 = new double[2];
            y0[0] = 20.0;
            y0[1] = 8.0;
            ob.RK4(10000, 2, 0.0, 0.5, y0);
            //RK4(int N, int M, double t0, double h, double[] y0)

        }
    }
}