我正在Haskell中实现N-Body模拟。 https://github.com/thorlucas/N-Body-Simulation
现在,每个粒子计算其力,然后相对于彼此的粒子加速。换句话说, O(n²)力的计算。如果我要计算每个组合一次,我可以将其降低到O(n选择2)。
let combs = [(a, b) | (a:bs) <- tails ps, b <- bs ]
force = map (\comb -> gravitate (fst comb) (snd comb)) combs
但我无法弄清楚如何在不使用状态的情况下将这些应用于粒子。在上面的示例中,ps
是[Particle]
其中
data Particle = Particle Mass Pos Vel Acc deriving (Eq, Show)
理论上,在有状态语言中,我只需循环组合,根据每个a
和b
的力计算相关加速度,然后更新每个Particle
ps
加速,因为我这样做。
我考虑过像foldr f ps combs
这样的事情。起始累加器将是当前ps
,而f
将是一个函数,它将获取每个comb
并更新Particle
中的相关ps
,并返回该累加器。对于这么简单的过程来说,这看起来非常耗费内存并且相当复杂。
有什么想法吗?
答案 0 :(得分:1)
从https://github.com/thorlucas/N-Body-Simulation
获取代码updateParticle :: Model -> Particle -> Particle
updateParticle ps p@(Particle m pos vel acc) =
let accs = map (gravitate p) ps
acc' = foldr (\(accx, accy) (x, y) -> (accx + x, accy + y)) (0, 0) accs
vel' = (fst vel + fst acc, snd vel + snd acc)
pos' = (fst pos + fst vel, snd pos + snd vel)
in Particle m pos' vel' acc'
step :: ViewPort -> Float -> Model -> Model
step _ _ ps = map (updateParticle ps) ps
并对其进行修改,以便加速在矩阵(井列表,列表......)中与更新每个粒子分开计算,我们得到......
updateParticle :: Model -> (Particle, [Acc]) -> Particle
updateParticle ps (p@(Particle m pos vel acc), accs) =
let acc' = foldr (\(accx, accy) (x, y) -> (accx + x, accy + y)) (0, 0) accs
vel' = (fst vel + fst acc, snd vel + snd acc)
pos' = (fst pos + fst vel, snd pos + snd vel)
in Particle m pos' vel' acc'
step :: ViewPort -> Float -> Model -> Model
step _ _ ps = map (updateParticle ps) $ zip ps accsMatrix where
accsMatrix = [map (gravitate p) ps | p <- ps]
...所以问题主要在于如何使用gravitate
= accsMatrix
的事实来减少gravitate a b
-1 * gravitate b a
的调用次数。 / p>
如果我们打印accsMatrix
,它看起来就像......
[[( 0.0, 0.0), ( 1.0, 2.3), (-1.0, 0.0), ...
[[(-1.0, -2.3), ( 0.0, 0.0), (-1.2, 5.3), ...
[[( 1.0, 0.0), ( 1.2, -5.3), ( 0.0, 0.0), ...
...
...所以我们看到accsMatrix !! i !! j == -1 * accsMatrix !! j !! i
。
因此,要使用上述事实,我们需要访问一些索引。首先,我们索引外部列表...
accsMatrix = [map (gravitate p) ps | (i,p) <- zip [0..] ps]
...并用列表理解替换内部列表......
accsMatrix = [[ gravitate p p' | p' <- ps] | (i,p) <- zip [0..] ps]
...通过zip获取更多索引...
accsMatrix = [[ gravitate p p' | (j, p') <- zip [0..] ps] | (i,p) <- zip [0..] ps]
...然后,关键是make accsMatrix
依赖于自身的一半矩阵......
accsMatrix = [[ if i == j then 0 else if i < j then gravitate p p' else -1 * accsMatrix !! j !! i | (j, p') <- zip [0..] ps] | (i, p) <- zip [0..] ps]
我们可以将它分开一点,如下所示......
accsMatrix = [[ accs (j, p') (i, p) | (j, p') <- zip [0..] ps] | (i, p) <- zip [0..] ps]
accs (j, p') (i, p)
| i == j = 0
| i < j = gravitate p p'
| otherwise = -1 * accsMatrix !! j !! i
...或使用map
accsMatrix = map (flip map indexedPs) $ map accs indexedPs
indexedPs = zip [0..] ps
accs (i, p) (j, p')
| i == j = 0
| i < j = gravitate p p'
| otherwise = -1 * accsMatrix !! j !! i
...或使用列表monad ...
accsMatrix = map accs indexedPs >>= (:[]) . flip map indexedPs
...虽然(对我而言)更难以了解这些内容。
此列表列表方法可能存在一些可怕的性能问题,尤其是使用!!
,以及由于遍历而您仍在运行 O(n²)操作的事实,以及O(n·(n - 1))≡O(n²)为@leftaroundabout提到的事实,但每次迭代都应该调用gravitate
n * (n-1) / 2
次。