dsmc-0.1.0.0: src/DSMC/Domain.hs
{-# LANGUAGE BangPatterns #-}
{-|
Domain operations: defining domains; free flow boundary conditions &
clipping for DSMC steps.
PRNG required to sample molecular velocities implies monadic interface
for most of operations. We use functions specifically typed for 'ST'.
-}
module DSMC.Domain
( Domain(..)
, getDimensions
, getCenter
, makeDomain
-- * Flow boundary
, initializeParticles
, openBoundaryInjection
, DomainSeeds
, clipToDomain
-- * Free volume calculation
, freeVolume
, freeVolumes
)
where
import Control.Monad.ST
import qualified Data.Array.Repa as R
import qualified Data.Vector.Unboxed as VU
import qualified Data.Vector as V
import System.Random.MWC
import System.Random.MWC.Distributions (normal)
import Control.Parallel.Stochastic
import DSMC.Particles
import DSMC.Traceables
import DSMC.Util
import DSMC.Util.Constants
import DSMC.Util.Vector
-- | Domain in which particles are spawned or system evolution is
-- simulated.
data Domain = Domain !Double !Double !Double !Double !Double !Double
-- ^ Rectangular volume, given by min/max value on every
-- dimension.
deriving Show
-- | Create a rectangular domain with center in the given point and
-- dimensions.
makeDomain :: Point
-- ^ Center point.
-> Double
-- ^ X dimension.
-> Double
-- ^ Y dimension.
-> Double
-- ^ Z dimension.
-> Domain
makeDomain !(x, y, z) !w !l !h =
let
xmin = x - w / 2
ymin = y - l / 2
zmin = z - h / 2
xmax = x + w / 2
ymax = y + l / 2
zmax = z + h / 2
in
Domain xmin xmax ymin ymax zmin zmax
{-# INLINE makeDomain #-}
-- | PRNG seeds used by particle generators.
type DomainSeeds = (Seed, Seed, Seed, Seed, Seed, Seed)
-- | Calculate width, length and height of a domain, which are
-- dimensions measured by x, y and z axes, respectively.
getDimensions :: Domain -> (Double, Double, Double)
getDimensions (Domain xmin xmax ymin ymax zmin zmax) =
(xmax - xmin, ymax - ymin, zmax - zmin)
{-# INLINE getDimensions #-}
-- | Calculate geometric center of a domain.
getCenter :: Domain -> Point
getCenter (Domain xmin xmax ymin ymax zmin zmax) =
(xmin + (xmax - xmin) / 2, ymin + (ymax - ymin) / 2, zmin + (zmax - zmin) / 2)
{-# INLINE getCenter #-}
-- | Volume of domain.
volume :: Domain -> Double
volume !(Domain xmin xmax ymin ymax zmin zmax) =
(xmax - xmin) * (ymax - ymin) * (zmax - zmin)
{-# INLINE volume #-}
-- | Sample new particles inside a domain.
--
-- PRNG state implies this to be a monadic action.
spawnParticles :: Domain
-> Flow
-> GenST s
-> ST s (VU.Vector Particle)
spawnParticles d@(Domain xmin xmax ymin ymax zmin zmax) flow g =
let
s = sqrt $ boltzmann * (temperature flow) / (mass flow)
(u0, v0, w0) = velocity flow
count = round $ (modelConcentration flow) * (volume d)
in do
VU.replicateM count $ do
u <- normal u0 s g
v <- normal v0 s g
w <- normal w0 s g
x <- uniformR (xmin, xmax) g
y <- uniformR (ymin, ymax) g
z <- uniformR (zmin, zmax) g
return $ ((x, y, z), (u, v, w))
-- | Pure version of 'spawnParticles'.
pureSpawnParticles :: Domain
-> Flow
-> Seed
-> (VU.Vector Particle, Seed)
pureSpawnParticles d flow s = purifyRandomST (spawnParticles d flow) s
-- | Fill the domain with particles for given flow parameters.
-- Particles inside the body are removed.
initializeParticles :: Domain
-> Flow
-> Body
-> Seed
-> (Ensemble, Seed)
initializeParticles d flow body s =
let
(res, s') = pureSpawnParticles d flow s
ens = fromUnboxed1 res
in
(filterEnsemble (not . inside body) ens, s')
-- | Sample new particles in 6 interface domains along each side of
-- rectangular simulation domain and add them to existing ensemble.
--
-- This function implements open boundary condition for
-- three-dimensional simulation domain.
--
-- Interface domains are built on faces of simulation domain using
-- extrusion along the outward normal of the face.
--
-- In 2D projection:
--
-- > +-----------------+
-- > | Interface1 |
-- > +--+-----------------+--+
-- > |I3| Simulation |I4|
-- > | | domain | |
-- > +--+-----------------+--+
-- > | I2 |
-- > +-----------------+
--
-- Particles in every interface domain are spawned in parallel using
-- Strategies.
openBoundaryInjection :: DomainSeeds
-> Domain
-- ^ Simulation domain.
-> Double
-- ^ Interface domain extrusion length.
-> Flow
-> Ensemble
-> (Ensemble, DomainSeeds)
openBoundaryInjection (s1, s2, s3, s4, s5, s6) domain ex flow ens =
let
(w, l, h) = getDimensions domain
(cx, cy, cz) = getCenter domain
d1 = makeDomain (cx - (w + ex) / 2, cy, cz) ex l h
d2 = makeDomain (cx + (w + ex) / 2, cy, cz) ex l h
d3 = makeDomain (cx, cy + (l + ex) / 2, cz) w ex h
d4 = makeDomain (cx, cy - (l + ex) / 2, cz) w ex h
d5 = makeDomain (cx, cy, cz - (h + ex) / 2) w l ex
d6 = makeDomain (cx, cy, cz + (h + ex) / 2) w l ex
v = [R.toUnboxed ens]
(new, (s1':s2':s3':s4':s5':s6':_)) =
unzip $
parMapST (\g d -> spawnParticles d flow g) $
zip [d1, d2, d3, d4, d5, d6] [s1, s2, s3, s4, s5, s6]
in
(fromUnboxed1 $ VU.concat (new ++ v), (s1', s2', s3', s4', s5', s6'))
-- | Filter out particles which are outside of the domain.
clipToDomain :: Domain -> Ensemble -> Ensemble
clipToDomain (Domain xmin xmax ymin ymax zmin zmax) ens =
let
-- | Check if particle is in the domain.
pred' :: Particle -> Bool
pred' !((x, y, z), _) =
xmax >= x && x >= xmin &&
ymax >= y && y >= ymin &&
zmax >= z && z >= zmin
{-# INLINE pred' #-}
in
filterEnsemble pred' ens
-- | Volume of a domain unoccupied by a given body, in m^3.
--
-- We use Monte Carlo method to calculate the approximate body volume
-- and then subtract it from the overall domain volume.
freeVolume :: Domain
-> Body
-> Int
-- ^ Use that many points to approximate the body volume.
-> GenST s
-> ST s (Double)
freeVolume d@(Domain xmin xmax ymin ymax zmin zmax) body testPoints g = do
points <- VU.replicateM testPoints $ do
x <- uniformR (xmin, xmax) g
y <- uniformR (ymin, ymax) g
z <- uniformR (zmin, zmax) g
return $ inside body ((x, y, z), (0, 0, 0))
let occupiedPoints = VU.length $ VU.filter id points
return $ (volume d) *
(fromIntegral (testPoints - occupiedPoints)) /
(fromIntegral testPoints)
-- | Sequential 'freeVolume' for a vector of domains.
freeVolumes :: Body
-> Int
-> GenST s
-> V.Vector Domain
-> ST s (VU.Vector Double)
freeVolumes body testPoints g doms =
VU.generateM (V.length doms)
(\i -> freeVolume (doms V.! i) body testPoints g)