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