N-тело с Yampa FRP, haskell

Я пытаюсь сделать решатель n-тел (куча объектов, гравитационно притягивающихся друг к другу). Проблема в том, что функцияgravity1 не возвращает возвращаемые объекты, что приводит к линейному движению объектов:

Код выглядит следующим образом:

    updateGame :: Game -> SF AppInput Game
    updateGame game = 
      proc input -> do
        ...
        objs        <- updateObjects' $ _foreground (Game._objects game) -< ()
    
        ...
    updateObjects' :: [Object] -> SF () [Object]
    updateObjects' objs =  parB . fmap (updateObject1 objs ) $ objs
    
    updateObject1 :: [Object] -> Object -> SF () Object
    updateObject1 objs0 obj0 =
      proc () -> do
        obj  <- gravity1 (objs0, obj0) -< ()
        returnA -< obj
    
    g = 6.673**(-11.0) :: Double
    
    gravity1 :: ([Object], Object) -> SF () (Object)    
    gravity1 (objs0, obj0) =
      proc () -> do
        let
          m0     =  _mass obj0               :: Double
          xform0 = (head . _transforms) obj0 :: M44 Double
          p0     = ( view (_w._xyz)) xform0  :: V3 Double
          
          ms     = foldr1 (+) $ fmap (_mass) objs0              :: Double
          xforms = fmap (head . _transforms) objs0              :: [M44 Double]
          ps     = foldr1 (^+^) $ fmap ( view (_w._xyz)) xforms :: V3 Double
    
          dir  = ps ^-^ p0                 :: V3 Double
          dist = norm dir                  :: Double
          f    = g * m0 * ms / dist**2.0   :: Double
          acc  = (f / ms) *^ (dir ^/ dist) :: V3 Double
          s    = 1000000000000000.0
          
        --vel <- ((_velocity obj0) ^+^) ^<< integral -< (s *^ acc)
        vel <- ((_velocity obj0) ^+^) ^<< integral -< (s *^ (DT.trace ("acc :" ++ show (s *^ acc)) $ acc))
    
        let mtx =
              mkTransformationMat
              rot
              tr
              where
                rot = (view _m33 xform0)
                tr  = vel + p0
          
        returnA -< obj0 { _transforms = [mtx]
                        , _velocity   = vel }

Запустив код, я прочитал вывод в консоли:

acc :V3 12105.49700148636 12105.49700148636 0.0
acc :V3 NaN NaN NaN
acc :V3 (-12105.49700148636) 12105.49700148636 0.0
acc :V3 12105.49700148636 12105.49700148636 0.0
acc :V3 NaN NaN NaN
acc :V3 (-12105.49700148636) 12105.49700148636 0.0
acc :V3 12105.49700148636 12105.49700148636 0.0
acc :V3 NaN NaN NaN
acc :V3 (-12105.49700148636) 12105.49700148636 0.0
acc :V3 12105.49700148636 12105.49700148636 0.0
acc :V3 NaN NaN NaN
acc :V3 (-12105.49700148636) 12105.49700148636 0.0
acc :V3 12105.49700148636 12105.49700148636 0.0
acc :V3 NaN NaN NaN
acc :V3 (-12105.49700148636) 12105.49700148636 0.0
acc :V3 12105.49700148636 12105.49700148636 0.0
acc :V3 NaN NaN NaN
acc :V3 (-12105.49700148636) 12105.49700148636 0.0
acc :V3 12105.49700148636 12105.49700148636 0.0
acc :V3 NaN NaN NaN

По сути, все значения одинаковы (значения NaN связаны с тем, что объект вычисляется против самого себя, что я должен исправить, но это не проблема здесь), похоже, что функцияgravity1 не возвращает возвращаемый объект, несмотря на то, что возвращаемое значение:

    returnA -< obj0 { _transforms = [mtx]
                    , _velocity   = vel }

Результатом является линейное движение объектов, поскольку acc кажется константой.

Я ожидаю, что после того, как gravity1 :: ([Object], Object) -> SF () (Object) обновит и вернет объект, updateObjects' objs = parB . fmap (updateObject1 objs ) $ objs и updateObject1 objs0 obj0 = ... returnA -< obj должны привести к обновлению всех объектов, а следующий итерационный цикл должен передать gravity1 :: ([Object], Object) -> SF () (Object) обновленный набор объектов, так что значение acc будет другим в каждом кадре. ..

Я неправильно понимаю логику того, как здесь все должно работать?


person madjestic    schedule 07.01.2021    source источник
comment
Интересно. Сделайте это минимально воспроизводимым примером.   -  person leftaroundabout    schedule 07.01.2021


Ответы (1)


Рассмотрим тип gravity1:

gravity1 :: ([Object], Object) -> SF () (Object)

Это функция, которая при наличии списка Object и определенного Object создаст сигнальную функцию. Эта сигнальная функция является так называемым генератором, что означает, что она может быть снабжена пустыми входными данными и непрерывно производить поток Object вывода.

Таким образом, по сути, gravity1 представляет собой два разных вида входных данных:

  • У него есть статические входные данные ([Object], Object), которые он получает один раз и навсегда остается неизменным, и
  • У него есть потоковые входные данные () (что эквивалентно отсутствию потокового ввода).

Имея это в виду, имеет смысл, что после передачи статических аргументов gravity1 будет создавать постоянный поток Object — в конце концов, он никогда не получает обновленные данные о том, где находятся какие-либо Object!

Чтобы выходной поток динамически реагировал на изменения положения объектов, эти положения должны быть потоковыми, а не статическими. В частности, вход [Object] должен быть потоковым:

gravity1 :: Object -> SF [Object] Object

Другой входной аргумент, указывающий начальную позицию интересующего нас Object, вероятно, не должен быть потоковым, но с ним нужно обращаться осторожно (это всего лишь начальная позиция после все).

Но если gravity1 принимает позиции Object в качестве потокового аргумента, как вы будете запускать его из updateObject? Скорее всего, вам потребуется использовать некоторую форму задержки , например:

updateObjects :: [Object] -> SF () [Object]
updateObjects objs0 = proc () -> do
  rec objs  <- iPre objs0 -< objs'
      objs' <- parB (fmap gravity1 objs0) -< objs
  returnA -< objs'

Между прочим, эта стратегия использования задержки (то есть ключевое слово rec вместе с iPre или чем-то подобным) — это именно то, что вам нужно использовать в gravity1 также для отслеживания текущего положения конкретного объекта, для которого вы вычисляете гравитацию. для.

person DDub    schedule 07.01.2021
comment
Спасибо, это демистифицирует магию. - person madjestic; 08.01.2021