通过净力计算位置,不同的dts产生不同的答案

时间:2016-12-16 15:51:24

标签: haskell physics

我正在尝试为充电和集合对象编写模拟器,只需计算每个对象上的净力,然后在用户指定的时间段内找到位置变化。

然而,我发现当我改变dt时,位置的变化是剧烈的,当它不应该显着改变时,减小dt应该让位置收敛于正确的答案。

例如,对象位于笛卡尔坐标(1,0,0)和(-1,0,0),质量为9e-31(电子质量),电荷为1库仑(不是电荷)我知道,电子的运行时间为0.1秒,dt为0.01秒,每个物体的位置总变化为2048米。然而,运行0.1秒和0.001秒的dt,位置变化约1.3e30米。这对我来说似乎相当离谱,但我在使用dt的部分中找不到任何问题。

我正在使用的代码(c / p以避免任何可能的更改)

import Data.List

main = print $ mainprog
    where

        mainprog = runUniverse makeUniverse 1 0.1

type Length = Double
type Mass = Double
type Charge = Double
type Time = Double



type Vector = (Double, Double, Double)
type Position = Vector
type Velocity = Vector
type Acceleration = Vector
type Force = Vector

data Widget = Widget {pos :: Position, mass :: Double, charge :: Double, velocity :: Velocity} deriving (Eq, Show, Read)


--utils
toScalar :: Vector -> Double
toScalar (x, y, z) = sqrt (x ^^ 2 + y ^^ 2 + z ^^ 2)

toUnit :: Vector -> Vector
toUnit (x, y, z) = (x / scalar, y / scalar, z / scalar)
   where 
       scalar = toScalar (x, y, z)

add :: Vector -> Vector -> Vector
add (x1, y1, z1) (x2, y2, z2) = (x1 + x2, y1 + y2, z1 + z2)

mult :: Vector -> Double -> Vector
mult (x, y, z) k = (k * x, k * y, k * z)

diff :: Vector -> Vector -> Vector
diff (x1, y1, z1) (x2, y2, z2) = (x1 - x2, y1 - y2, z1 - z2)


--calcs
gForce :: Widget -> Widget -> Force
gForce (Widget pos1 mass1 _ _) (Widget pos2 mass2 _ _) = mult unitForce scalarForce
    where
        unitForce = toUnit posdiff
        scalarForce = (g * mass1 * mass2) / (radius ^^ 2)
        g = 6.674e-11
        radius = toScalar posdiff
        posdiff = diff pos1 pos2


eForce :: Widget -> Widget -> Force
eForce (Widget pos1 _ charge1 _) (Widget pos2 _ charge2 _) = mult unitForce scalarForce
    where 
        unitForce = (toUnit posdiff) 
        --necessary to determine attraction vs repulsion, whereas        gravitational is always attractive
        scalarForce = ((abs (k_C * charge1 * charge2)) / (radius ^^ 2)) * (signum charge1) * (signum charge2)
        k_C = 8.988e9
        radius = toScalar posdiff
        posdiff = diff pos1 pos2


netForce :: [Force] -> Force
netForce = foldl add (0, 0, 0)

toAccel :: Force -> Widget -> Acceleration
toAccel f (Widget _ mass _ _)  = mult f (1/mass) 

newVeloc :: Velocity -> Acceleration -> Time -> Velocity
newVeloc v a dt = add v (mult a dt)

newPos :: Vector -> Velocity -> Time -> Vector
newPos s v dt = add s (mult v dt)



newWidget :: Widget -> Position -> Velocity -> Widget
newWidget (Widget pos1 mass charge vel1) pos2 vel2 = Widget pos2 mass charge vel2

tUniverse :: [Widget] -> Time -> [Widget]
tUniverse widgets dt = zipWith3 newWidget widgets poses vels
    where

        netMassForces = map (\w -> gForcePrime w (widgets \\ [w])) widgets
        gForcePrime w ws = netForce $ map (gForce w) ws

        netElectricForces = map (\w -> eForcePrime w (widgets \\ [w])) widgets
        eForcePrime w ws = netForce $ map (eForce w) ws

        volds = map velocity widgets

        polds = map pos widgets

        accels = zipWith toAccel (map netForce (zipWith (\a b -> a : [b]) netMassForces netElectricForces)) widgets

        vels = zipWith (\v a -> newVeloc v a dt) volds accels

        poses = zipWith (\s v -> newPos s v dt) polds vels


makeUniverse :: [Widget]
makeUniverse = [(Widget (-1, 0, 0) 1 1 (0, 0, 0)), (Widget (1, 0, 0) 1 1 (0, 0, 0))]

runUniverse :: [Widget] -> Time -> Time -> [Widget]
runUniverse ws t dt
    | t <= 0 = ws
    | otherwise = runUniverse (tUniverse (inelasticCollide ws) dt) (t-dt) dt  



inelasticCollide :: [Widget] -> [Widget]
inelasticCollide [] = [] 
inelasticCollide (w:[]) = [w]
inelasticCollide (w:ws) = (combine w (sameposes w ws)) : (inelasticCollide $ ws \\ (sameposes w ws))
    where
        sameposes w ws = filter (\w' -> pos w == pos w') ws

        combine :: Widget -> [Widget] -> Widget
        combine = foldl (\(Widget pos mass1 charge1 veloc1) (Widget _ mass2 charge2 veloc2) -> Widget pos (charge1 + charge2) (mass1 + mass2) (newveloc mass1 mass2 veloc1 veloc2))
        --inelastic collision, m1v1 + m2v2 = m3v3 therefore v3 = (m1v1 + m2v2)/(m1 + m2)
        newveloc m1 m2 v1 v2 = ((v1 `mult` m1) `add` (v2 `mult` m2)) `mult` (1 / (m1 + m2))

我所知道的问题是在tUniverse函数中,可能在某些加速度,速度或位置(加速度,力量或姿势)的计算中。我尝试通过将每个乘以dt的倒数来改变为Accel,newVeloc和newPos,但它没有显着改变输出。

随意忽略inelasticCollide,我可能用id函数替换它,但我只是把它留在了因为它在某些时候是相关的。

编辑:我已经更新了代码以修复加速度的错误计算,非弹性转换中的质量和电荷切换,以及使用dpos / dvel进行重复计算,但我仍然发现我收到错误例如,每次充电为1 C,dt = 0.01时为~10 ^ 8,dt = 0.1时为~10 ^ 7,每次充电为0.01 C,~250对于dt = 0.01,对于dt = 0.1,为~65。

1 个答案:

答案 0 :(得分:2)

似乎“明显”的问题是newWidget假设dposdvel是增量,但是当它在tUniverse poses和{{ 1}}实际上已经完成了添加。

要调试我已经重写了要使用vels的东西,认为某个地方可能存在不匹配。事实证明,群体和指控在newtypes中被转换,但这对我的测试案例无关紧要。我发现这个问题的方法是添加inelasticCollide并看到当速度分量为1时,对象的位置分量会使每个勾号加倍。

我不知道其他计算是否准确。

trace