Barnes-Hut: tree management

This commit is contained in:
Dimitri Lozeve 2017-11-07 10:30:29 +00:00
parent 6e4cf862d4
commit 232fbc6701
3 changed files with 122 additions and 11 deletions

View file

@ -55,8 +55,8 @@ csvFromPoint (P v) = csvFromVector v
csvFromBody :: Double -> Body -> String csvFromBody :: Double -> Body -> String
csvFromBody dt b = csvFromBody dt b =
show dt ++ "," ++ show dt ++ "," ++
csvFromPoint (bodyPosition b) ++ "," ++ csvFromPoint (_bodyPosition b) ++ "," ++
csvFromVector (bodySpeed b) ++ "\n" csvFromVector (_bodySpeed b) ++ "\n"
-- | Show a list of bodies as CSV -- | Show a list of bodies as CSV
csvFromBodies :: Double -> [Body] -> String csvFromBodies :: Double -> [Body] -> String

View file

@ -18,6 +18,7 @@ library
exposed-modules: Lib exposed-modules: Lib
build-depends: base >= 4.7 && < 5 build-depends: base >= 4.7 && < 5
, linear , linear
, lens
default-language: Haskell2010 default-language: Haskell2010
executable orbit-exe executable orbit-exe

View file

@ -5,12 +5,23 @@ Copyright : (c) Dimitri Lozeve, 2017
License : BSD3 License : BSD3
Maintainer : dimitri.lozeve@gmail.com Maintainer : dimitri.lozeve@gmail.com
-} -}
{-# LANGUAGE TemplateHaskell #-}
module Lib ( module Lib (
-- * Constants -- * Constants
gravity, gravity,
-- * Body type -- * Body type
Body(..), Body(..),
bodyDistance, bodyDistance,
-- * Barnes-Hut
Region(..),
Octree(..),
Octant(..),
selectOctant,
subOctree,
updateRegion,
insertBody,
-- * Gravity force -- * Gravity force
field, field,
acceleration, acceleration,
@ -26,6 +37,8 @@ import Linear.V3
import Linear.Affine import Linear.Affine
import Linear.Metric import Linear.Metric
import Control.Lens hiding (Empty)
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- CONSTANTS -- CONSTANTS
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -40,18 +53,115 @@ gravity = 6.67408e-11
-- | Body type -- | Body type
data Body = Body { data Body = Body {
bodyName :: String, -- ^ Name _bodyName :: String, -- ^ Name
bodyRadius :: Double, -- ^ Radius [m] _bodyRadius :: Double, -- ^ Radius [m]
bodyMass :: Double, -- ^ Mass [kg] _bodyMass :: Double, -- ^ Mass [kg]
bodyPosition :: Point V3 Double, -- ^ Position [m] _bodyPosition :: Point V3 Double, -- ^ Position [m]
bodySpeed :: V3 Double -- ^ Speed [m/s] _bodySpeed :: V3 Double -- ^ Speed [m/s]
} deriving (Show, Eq) } deriving (Show, Eq)
makeLenses ''Body
-- | Distance between two bodies -- | Distance between two bodies
bodyDistance :: Body -> Body -> Double bodyDistance :: Body -> Body -> Double
bodyDistance body1 body2 = bodyDistance body1 body2 =
distance (bodyPosition body1) (bodyPosition body2) distance (_bodyPosition body1) (_bodyPosition body2)
--------------------------------------------------------------------------------
-- BARNES-HUT
--------------------------------------------------------------------------------
-- | Region type, represents a region in space
data Region = Region {
_regionCenter :: Point V3 Double,
_regionCenterOfMass :: Point V3 Double,
_regionMass :: Double,
_regionDiameter :: Double
} deriving (Show, Eq)
makeLenses ''Region
-- | Main data structure for the Octree
data Octree = Empty Region
| Single Region Body
| Node Region Octree Octree Octree Octree Octree Octree Octree Octree
deriving (Show, Eq)
-- | One of the 8 octants from a given reference point
data Octant = NED | NWD | SWD | SED
| NEU | NWU | SWU | SEU
deriving (Show, Eq)
-- | Return the octant in which is located a body
selectOctant :: Point V3 Double -> Body -> Octant
selectOctant center body = aux $ _bodyPosition body .-. center
where aux (V3 x y z) = case (x > 0, y > 0, z > 0) of
(True, True, True) -> NEU
(True, True, False) -> NED
(True, False, True) -> SEU
(True, False, False) -> SED
(False, True, True) -> NWU
(False, True, False) -> NWD
(False, False, True) -> SWU
(False, False, False) -> SWD
-- | Create a subtree for a given Region and Octant
subOctree :: Region -> Octant -> Octree
subOctree r octant =
Empty (r & regionDiameter .~ newdiameter
& regionCenter %~ (+ ((newdiameter/2) *^ centershift)))
where newdiameter = (r ^. regionDiameter) / 2
centershift = case octant of
NED -> P $ V3 1 1 (-1)
NWD -> P $ V3 (-1) 1 (-1)
SWD -> P $ V3 (-1) (-1) (-1)
SED -> P $ V3 1 (-1) (-1)
NEU -> P $ V3 1 1 1
NWU -> P $ V3 (-1) 1 1
SWU -> P $ V3 (-1) (-1) 1
SEU -> P $ V3 1 (-1) 1
-- | Update the mass and the center of mass of a region when adding a
-- new Body
updateRegion :: Body -> Region -> Region
updateRegion b r =
r & regionMass .~ newmass
& regionCenterOfMass .~ newcenter
where newmass = _regionMass r + _bodyMass b
newcenter = ((_regionMass r *^ _regionCenterOfMass r) + (_bodyMass b *^ _bodyPosition b))
^/ (_regionMass r + _bodyMass b)
-- | Insert a new body in an Octree
insertBody :: Body -> Octree -> Octree
insertBody b t = case t of
-- If it is empty, we turn it into a singleton Region, adjusting its
-- mass and center of mass. However, if the body is too far away
-- (i.e. outside the diameter of the Region), we just ignore it.
Empty r -> if distance (_bodyPosition b) (_regionCenter r) > (_regionDiameter r / 2)
then Empty r
else Single (updateRegion b r) b
-- If it is a singleton, we create the 8 subtrees and we insert the
-- two bodies in them. We will end up in the Node case below.
Single r b' ->
insertBody b $ insertBody b' $
Node r (subOctree r NED) (subOctree r NWD) (subOctree r SWD) (subOctree r SED)
(subOctree r NEU) (subOctree r NWU) (subOctree r SWU) (subOctree r SEU)
-- Finally, if it is already a tree, we have to choose in which
-- octant inserting the new body.
Node r ned nwd swd sed neu nwu swu seu ->
let r' = updateRegion b r
in
case (selectOctant (r ^. regionCenter) b) of
NED -> Node r' (insertBody b ned) nwd swd sed neu nwu swu seu
NWD -> Node r' ned (insertBody b nwd) swd sed neu nwu swu seu
SWD -> Node r' ned nwd (insertBody b swd) sed neu nwu swu seu
SED -> Node r' ned nwd swd (insertBody b sed) neu nwu swu seu
NEU -> Node r' ned nwd swd sed (insertBody b neu) nwu swu seu
NWU -> Node r' ned nwd swd sed neu (insertBody b nwu) swu seu
SWU -> Node r' ned nwd swd sed neu nwu (insertBody b swu) seu
SEU -> Node r' ned nwd swd sed neu nwu swu (insertBody b seu)
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -62,15 +172,15 @@ bodyDistance body1 body2 =
field :: Body -> Point V3 Double -> V3 Double field :: Body -> Point V3 Double -> V3 Double
field body pos = field body pos =
unP $ (gravity * m / r**2) *^ normalize vec unP $ (gravity * m / r**2) *^ normalize vec
where m = bodyMass body where m = _bodyMass body
vec = bodyPosition body - pos vec = _bodyPosition body - pos
r = norm vec r = norm vec
-- | Acceleration given to a body by its neighbours -- | Acceleration given to a body by its neighbours
acceleration :: Body -> [Body] -> V3 Double acceleration :: Body -> [Body] -> V3 Double
acceleration body = foldr f 0 acceleration body = foldr f 0
where f neighbour acc = where f neighbour acc =
acc + field neighbour (bodyPosition body) acc + field neighbour (_bodyPosition body)
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------