Haskell中的Verlet集成

Haskell中的Verlet集成,haskell,lambda,numerical-methods,verlet-integration,Haskell,Lambda,Numerical Methods,Verlet Integration,我是Haskell的新手,作为练习,我一直在尝试实现Joel Franklin的《物理计算方法》一书中的一些代码(用Mathematica编写)。我编写了以下代码,将lambda表达式(加速度)作为第一个参数。一般来说,加速度的形式为x'=f(x',x,t),但并不总是所有三个变量 -- Implementation 2.1: The Verlet Method verlet _ _ _ _ 0 = [] verlet ac x0 v0 dt n = take n $ zip [0,d

我是Haskell的新手,作为练习,我一直在尝试实现Joel Franklin的《物理计算方法》一书中的一些代码(用Mathematica编写)。我编写了以下代码,将lambda表达式(加速度)作为第一个参数。一般来说,加速度的形式为x'=f(x',x,t),但并不总是所有三个变量

-- Implementation 2.1: The Verlet Method
verlet _  _  _  _  0 = [] 
verlet ac x0 v0 dt n = take n $ zip [0,dt..] $ verlet' ac x0 v0 0 dt
  where verlet' ac x0 v0 t0 dt = 
          let
            xt = x0 + dt*v0 + 0.5*(dt^2)*(ac x0 v0 t0)
            vt = v0 + dt*(ac x0 v0 t0)
          in
            xt:(verlet' ac xt vt (t0+dt) dt)
在ghci中,我将使用以下命令运行此代码(加速度函数a=-(2pi)2x来自本书中的一个练习):

我的问题是,这并不是真正的Verlet方法——这里xn+1=xn+dt*vn+1/2*a(xn,vn,n),而维基百科中描述的Verlet方法是xn+1=2*xn-xn-1+a(xn,vn,n)。如何重新编写此函数以更忠实地表示Verlet集成方法


切题地说,有没有办法写得更优雅简洁?有没有线性代数库可以让这更容易?非常感谢您的建议。

忠实的Verlet序列的xn取决于前面两个值x--xn-1和xn-2。这种序列的一个典型例子是斐波那契序列,它有一个线性Haskell定义:

fibs :: [Int]
fibs = 0 : 1 : zipWith (+) fibs     (tail fibs)
                        -- f_(n-1)  f_n
这将斐波那契序列定义为无限(惰性)列表。对
tail fibs
的自我引用给出了前面的术语,而对
fibs
的引用给出了前面的术语。然后将这些术语与
(+)
组合,生成序列中的下一个术语

您可以采取以下相同的方法解决您的问题:

type State = (Double, Double, Double)  -- (x, v, t) -- whatever you need here

step :: State -> State -> State
step s0 s1 = -- determine the next state based on the previous two states

verlet :: State -> State -> [State]
verlet s0 s1 = ss
  where ss = s0 : s1 : zipWith step ss (tail ss)
数据结构
State
保存您需要的任何状态变量-x,v,t,n。。。
函数
步骤
类似于Fibonacci情况下的
(+)
,并计算给定前两个步骤的下一个状态。
verlet
函数确定给定初始两个状态的整个状态序列。

实际上,如果你继续阅读,你会发现这两个变体都出现在维基百科页面上


基本维莱特 二阶常微分方程x'(t)=a(x(t))的基本二阶中心差商离散化为

xn+1-2*xn+xn-1=an*dt^2

注意,迭代中没有速度,加速度函数a(x)中也没有速度。这是因为,当动力系统是保守的,也就是说,-m*a(x)是一些势函数的梯度,而势函数是静态对象时,Verlet积分才优于其他积分方法,它们只依赖于位置,不依赖于时间,也不依赖于速度。许多无摩擦机械系统属于这一类


Verlet速度 现在,使用一阶中心差分商,设置时间tn到的速度

vn*(2*dt)=xn+1-xn-1

在第一个方程中加上,减去,得到

-2*xn+2*xn-1=-2*vn*dt+an*dt^2

2*xn+1-2*xn=2*vn*dt+an*dt^2

vn=(xn-xn-1)/dt+0.5*an*dt

xn+1=xn+vn*dt+0.5*an*dt^2

这是编写velocity Verlet算法的一个变体


(更新以使所有状态变量在迭代步骤前后的同一时间对应)

利用前一步从n-1到n的方程,可以在速度计算中用vn-1和an-1代替xn-1。然后

vn=vn-1+0.5*(an-1+an)*dt

为了避免任何向量x、v、a的两个实例,可以安排更新过程,以便一切就绪。假设在迭代步骤的入口,存储的数据对应于(tn-1、xn-1、vn-1、an-1)。然后,下一个状态计算为

vn-0.5=vn-1+0.5*an-1*dt

xn=xn-1+vn-0.5*dt

使用xn和vn-0.5进行碰撞检测

an=a(xn)

vn=vn-0.5+0.5*an*dt

使用xn和vn进行统计

或者作为代码

v += a*0.5*dt;
x += v*dt;
do_collisions(x,v);
a = eval_a(x);
v += a*0.5*dt;
do_statistics(x,v); 
改变这些操作的顺序将破坏Verlet方案,并显著改变结果,不透明度的旋转是可能的,但在迭代步骤后必须小心状态的解释

唯一需要的初始化是a0=a(x0)的计算


蛙跳小花 从velocity Verlet的公式可以看出,对于位置的更新,不需要速度vn,而只需要半点速度vn+0.5。然后

an=a(xn)

vn+0.5=vn-0.5+an*dt

xn+1=xn+vn+0.5*dt

还是用代码

a = eval_a(x);
v += a*dt;
x += v*dt;
同样,这些操作的顺序从根本上来说是重要的,对于保守的系统来说,变化将导致奇怪的结果


更新)但是,可以将执行序列旋转到

x += v*dt;
a = eval_a(x);
v += a*dt;
这对应于三元组(tn、xn、vn+0.5)的迭代,如中所示

xn=xn-1+vn-0.5*dt

an=a(xn)

vn+0.5=vn-0.5+an*dt

初始化只需要计算

v0+0.5=v0+0.5*a(x0)*dt

(完)


由于时间指数不匹配,使用xn和vn-0.5或vn+0.5计算的任何统计数据将因与dt成比例的误差而关闭。碰撞可以单独用位置向量检测,但在偏转中,速度也需要敏感地更新。

在执行user5402的建议后,我的解决方案如下:

-- 1-Dimensional Verlet Method
type State = (,,) Double Double Double -- x, x', t

first :: State -> Double
first (x, _, _) = x

second :: State -> Double
second (_, x, _) = x

third :: State -> Double
third (_, _, x) = x

verlet1 :: (State -> Double) -> State -> Double -> Int -> [State]
verlet1 f s0 dt n = take n ss
  where
    ss = s0 : s1 : zipWith step ss (tail ss)
      where 
        s1 = (first s0 + dt*(second s0) + 0.5*dt^2*(f s0), 
              second s0 + dt*(f s0), third s0 + dt)
        step :: State -> State -> State
        step s0 s1 = (2*(first s1) - first s0 + dt^2*(f s1), 
                      second s1 + dt*(f s1), third s1 + dt)
我使用以下命令在ghci中运行它:

verlet1(\x->-(2*pi)^2*(第一个x))(1,0,0)0.01 100


这似乎正是我所期待的——显然是正弦运动!我还没有绘制x(如果有人对如何在Haskell中绘制x有任何建议,欢迎)。此外,如果您看到任何明显的重构,请随时指出它们。谢谢

这是一个很好的解决方案,但当我尝试将其加载到ghci中使用它时,会收到以下错误消息:
parse error in constructor in data/newtype declaration:(Double,Do
-- 1-Dimensional Verlet Method
type State = (,,) Double Double Double -- x, x', t

first :: State -> Double
first (x, _, _) = x

second :: State -> Double
second (_, x, _) = x

third :: State -> Double
third (_, _, x) = x

verlet1 :: (State -> Double) -> State -> Double -> Int -> [State]
verlet1 f s0 dt n = take n ss
  where
    ss = s0 : s1 : zipWith step ss (tail ss)
      where 
        s1 = (first s0 + dt*(second s0) + 0.5*dt^2*(f s0), 
              second s0 + dt*(f s0), third s0 + dt)
        step :: State -> State -> State
        step s0 s1 = (2*(first s1) - first s0 + dt^2*(f s1), 
                      second s1 + dt*(f s1), third s1 + dt)