Haskell粒子模拟 - 计算粒子速度

3
我正在使用Haskell开发一个粒子模拟程序。其中一个函数是基于周围所有粒子的质量和速度来确定模拟中所有粒子的新速度。
该函数形式为:
accelerate :: Float -> [Particle] -> [Particle]

“Particle”是一种数据类型,包含质量、位置向量和速度向量,“Float”参数表示模拟中相应时间步长的增量。
我想得到一些关于在计算列表中每个粒子相对于其他粒子的速度时可以使用的可能函数的建议。
我能想到的一个可能的方法是:
  1. assume there is another function 'velocityCalculator' which has the following definition:

    velocityCalculator :: Particle -> Particle -> (Float,Float)
    
这个操作需要两个粒子,并返回第一个粒子更新后的速度向量。
  1. apply foldl; using the above function as the binary operator, a particle and the list of particles as the arguments, i.e.

    foldl velocityCalculator particle particleList
    
  2. iterate through the list of particle, applying foldl to each element and building the new list containing the particles with the updated velocities

我不确定这是否是最有效的方法,所以非常感谢任何建议和改进。

请注意 -> 我只是寻求建议,而不是答案!

谢谢!


您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - felipez
没有什么特别具体的,我只是在寻找Haskell中可能更快地完成应用velocityCalculator函数于列表中的可能的高阶函数。 - dfj328
2个回答

3
似乎你已经决定使用`foldl`了。例如:
  1. 遍历粒子列表,对每个元素应用`foldl`并构建包含更新速度的粒子的新列表
这并没有什么意义。你将`foldl`应用于列表以将其减少为“汇总值”,根据某些二进制汇总函数。将其应用于单个粒子并不是很有意义。
我回答这个问题是假设你在编写程序方面遇到了麻烦——通常最好先解决这个问题,再考虑效率问题。如果我做出了错误的假设,请让我知道。
我不确定你想要使用什么规则来更新速度,但我假设它是某种成对力模拟,例如重力或电磁学。如果是这样,下面是一些提示,可以帮助你找到解决方案。
type Vector = (Float, Float)

-- Finds the force exerted on one particle by the other.
-- Your code will be simplified if this returns (0,0) when the two
-- particles are the same. 
findForce :: Particle -> Particle -> Vector

-- Find the sum of all forces exerted on the particle
-- by every particle in the list. 
totalForce :: [Particle] -> Particle -> Vector

-- Takes a force and a particle to update, returns the same particle with
-- updated velocity. 
updateVelocity :: Vector -> Particle -> Particle

-- Calculate mutual forces and update all particles' velocities
updateParticles :: [Particle] -> [Particle]

每个函数都将非常简短,只有一两行。如果您需要更多关于使用哪些高阶函数的提示,请注意您正在尝试编写的函数类型,并注意。
map :: (a -> b) -> [a] -> [b]           -- takes a list, returns a list
filter :: (a -> Bool) -> [a] -> [a]     -- takes a list, returns a list
foldl :: (a -> b -> a) -> a -> [b] -> a -- takes a list, returns something else
foldr :: (a -> b -> b) -> b -> [a] -> b -- takes a list, returns something else

2

如果您为每个Particle分配一个particle_id :: Int ID并定义如下内容,则可以通过记忆化实现加速2倍:

forceOf a b | particle_id a > particle_id b = -(forceOf b a)
            | otherwise = (pos a - pos b) *:. charge a * charge b / norm (pos a - pos b) ^ 3

在这里,(*:.) :: Vector -> Double -> Vector 是向量数量乘法,因此以上是一个1/r^2的力学定律。请注意,在这里我们进行了pos a - pos b的记忆化,然后我们也进行了forceOf a b的记忆化,以便用作forceOf b a

然后,您可以使用dvs = [dt * sum (map (forceOf a) particles) / mass a | a <- particles]来获得速度变化的列表,然后使用zipWith (+) (map velocity particles) dvs

其中一个问题是,这种方法不太能应对数值不确定性:所有关于时间$t+1$的信息都基于时间$t$的真实条件。您可以通过解决矩阵方程来解决这个问题;与其使用$v_+ = v + dt * M v$(其中$v=v(t)$且$v_+=v(t+1)$),您可以写成$v_+ = v + dt * M v_+$,以便您可以得到$v_+ = (1 − dt * M)-1 v$。这可能更加数值稳定。甚至更好的方法是将两种解决方案50/50混合。$v_+ = v + dt * M (v + v_+)/2$。这里有很多选择。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接