Haskell中的Verlet集成

时间:2014-03-09 05:51:21

标签: 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,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) 2 x来自书中的练习):

verlet (\x v t -> -(2*pi)^2*x) 1 0 0.01 1000

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

Tangentially,有没有办法更优雅,更简洁地写出来?是否有线性代数库可以使这更容易?我感谢你的建议。

3 个答案:

答案 0 :(得分:5)

忠实的Verlet序列具有x n ,具体取决于前两个x - x n-1 和x n-2 的值。这种序列的典型例子是Fibonacci序列,它具有这种单行Haskell定义:

fibs :: [Int]
fibs = 0 : 1 : zipWith (+) fibs     (tail fibs)
                        -- f_(n-1)  f_n

这将Fibonacci序列定义为无限(懒惰)列表。 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,... 函数step类似于Fibonacci案例中的(+),并计算给定前两个的下一个状态。给定初始两个状态时,verlet函数确定整个状态序列。

答案 1 :(得分:2)

实际上,如果您继续阅读,您会发现两个变体都在维基百科页面上显示。


基本Verlet

二阶ODE x''(t)= a(x(t))的基本二阶中心差商离散化是

  

x n + 1 - 2 * x n + x n-1 = a n * dt ^ 2

请注意,迭代中没有速度,加速函数a(x)也没有。这是因为当动态系统保守时,Verlet集成仅优于其他集成方法,即-m * a(x)是某些潜在函数的梯度,潜在函数是静态对象,它们仅依赖于位置,而不是准时而不是速度。许多无摩擦机械系统属于这一类。


Velocity Verlet

现在使用一阶中心差分商设定时间t n 的速度

  

v n *(2 * dt)= x n + 1 - x n-1

并在第一个等式中加上和减去这个以获得

  

-2 * x n + 2 * x n-1 = -2 * v n * dt + a n * dt的^ 2

     

2 * x n + 1 - 2 * x n = 2 * v n * dt + a n * dt的^ 2

  

v n =(x n - x n-1 )/ dt + 0.5 * a n * dt的

     

x n + 1 = x n + v n * dt + 0.5 * a n * dt ^ 2

这是编写velocity-Verlet算法的一种变体。


更新以使所有状态变量对应于迭代步骤之前和之后的同一时间

使用从n-1到n的前一步骤的等式,可以用v n-1 n-替换x n-1 1 在速度计算中。然后

  

v n = v n-1 + 0.5 *(a n-1 + a n ) * dt的

为了避免任何向量x,v,a的两个实例,可以安排更新过程以使一切都到位。假设在进入迭代步骤时,存储的数据对应于(t n-1 ,x n-1 ,v n-1 ,一个<子> N-1 )。然后下一个状态计算为

  

v n-0.5 = v n-1 + 0.5 * a n-1 * dt

     

x n = x n-1 + v n-0.5 * dt

     

使用x n 进行碰撞检测并且v n-0.5

     

a n = a(x n

     

v n = v n-0.5 + 0.5 * a n * dt

     

使用x n 和v n

进行统计

或代码

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方案并显着改变结果,可以旋转操作,但是必须注意迭代步骤后对状态的解释。

唯一需要的初始化是计算 0 = a(x 0 )。


Leapfrog Verlet

从速度Verlet的公式可以看出,对于位置的更新,人们不需要速度v n ,而只需要半点速度v n + 0.5 。然后

  

a n = a(x n

     

v n + 0.5 = v n-0.5 + a n * dt

     

x n + 1 = x n + v n + 0.5 * dt

或代码

a = eval_a(x);
v += a*dt;
x += v*dt;

同样,这些操作的顺序从根本上讲是重要的,更改将导致保守系统的奇怪结果。


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

x += v*dt;
a = eval_a(x);
v += a*dt;

这对应于三元组的迭代(t n ,x n ,v n + 0.5 ),如

  

x n = x n-1 + v n-0.5 * dt

     

a n = a(x n

     

v n + 0.5 = v n-0.5 + a n * dt

初始化只需要计算

  

v 0 + 0.5 = v 0 + 0.5 * a(x 0 )* dt

结束更新

使用x n 和v n-0.5 或v n + 0.5 进行计算的任何统计数据都将被一个与dt,因为时间指数不匹配。仅使用位置矢量就可以检测到碰撞,但是在偏转中,速度也需要明智地更新。

答案 2 :(得分:0)

这是我在实施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*(first x)) (1, 0, 0) 0.01 100

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