aivika-experiment-3.0: Simulation/Aivika/Experiment/Types.hs
{-# LANGUAGE TypeFamilies, MultiParamTypeClasses #-}
-- |
-- Module : Simulation.Aivika.Experiment.Types
-- Copyright : Copyright (c) 2012-2014, David Sorokin <david.sorokin@gmail.com>
-- License : BSD3
-- Maintainer : David Sorokin <david.sorokin@gmail.com>
-- Stability : experimental
-- Tested with: GHC 7.8.3
--
-- The module defines the simulation experiments. They automate
-- the process of generating and analyzing the results. Moreover,
-- this module is open to extensions, allowing you to define
-- your own output views for the simulation results, for example,
-- such views that would allow saving the results in PDF or as
-- charts. To decrease the number of dependencies, such possible
-- extenstions are not included in this package, although simple
-- views are provided.
--
module Simulation.Aivika.Experiment.Types where
import Control.Monad
import Control.Monad.State
import Control.Concurrent.ParallelIO.Local
import Data.Maybe
import Data.Monoid
import System.Directory
import System.FilePath
import GHC.Conc (getNumCapabilities)
import Simulation.Aivika
import Simulation.Aivika.Experiment.ExperimentWriter
-- | It defines the simulation experiment with the specified rendering backend and its bound data.
data Experiment =
Experiment { experimentSpecs :: Specs,
-- ^ The simulation specs for the experiment.
experimentTransform :: ResultTransform,
-- ^ How the results must be transformed before rendering.
experimentLocalisation :: ResultLocalisation,
-- ^ Specifies a localisation applied when rendering the experiment.
experimentRunCount :: Int,
-- ^ How many simulation runs should be launched.
experimentDirectoryName :: ExperimentFilePath,
-- ^ The directory in which the output results should be saved.
experimentTitle :: String,
-- ^ The experiment title.
experimentDescription :: String,
-- ^ The experiment description.
experimentVerbose :: Bool,
-- ^ Whether the process of generating the results is verbose.
experimentNumCapabilities :: IO Int
-- ^ The number of threads used for the Monte-Carlo simulation
-- if the executable was compiled with the support of multi-threading.
}
-- | The default experiment.
defaultExperiment :: Experiment
defaultExperiment =
Experiment { experimentSpecs = Specs 0 10 0.01 RungeKutta4 SimpleGenerator,
experimentTransform = id,
experimentLocalisation = englishResultLocalisation,
experimentRunCount = 1,
experimentDirectoryName = UniqueFilePath "experiment",
experimentTitle = "Simulation Experiment",
experimentDescription = "",
experimentVerbose = True,
experimentNumCapabilities = getNumCapabilities }
-- | It allows rendering the simulation results in an arbitrary way.
class ExperimentRendering r where
-- | Defines a context used when rendering the experiment.
data ExperimentContext r :: *
-- | Render the experiment after the simulation is finished, for example,
-- creating the @index.html@ file in the specified directory.
renderExperiment :: Experiment -> r -> [ExperimentReporter r] -> FilePath -> ExperimentWriter ()
-- | This is a generator of the reporter with the specified rendering backend.
data ExperimentGenerator r =
ExperimentGenerator { generateReporter :: Experiment -> r -> FilePath -> ExperimentWriter (ExperimentReporter r)
-- ^ Generate a reporter bound up with the specified directory.
}
-- | Defines a view in which the simulation results should be saved.
-- You should extend this type class to define your own views such
-- as the PDF document.
class ExperimentRendering r => ExperimentView v r where
-- | Create a generator of the reporter.
outputView :: v -> ExperimentGenerator r
-- | It describes the source simulation data used in the experiment.
data ExperimentData =
ExperimentData { experimentResults :: Results,
-- ^ The simulation results used in the experiment.
experimentPredefinedSignals :: ResultPredefinedSignals
-- ^ The predefined signals provided by every model.
}
-- | Defines what creates the simulation reports by the specified renderer.
data ExperimentReporter r =
ExperimentReporter { reporterInitialise :: ExperimentWriter (),
-- ^ Initialise the reporting before
-- the simulation runs are started.
reporterFinalise :: ExperimentWriter (),
-- ^ Finalise the reporting after
-- all simulation runs are finished.
reporterSimulate :: ExperimentData -> Event DisposableEvent,
-- ^ Start the simulation run in the start time
-- and return a finalizer that will be called
-- in the stop time after the last signal is
-- triggered and processed.
reporterContext :: ExperimentContext r
-- ^ Return a context used by the renderer.
}
-- | Run the simulation experiment sequentially. For example,
-- it can be a Monte-Carlo simulation dependentent on the external
-- 'Parameter' values.
runExperiment :: ExperimentRendering r
=> Experiment
-- ^ the simulation experiment to run
-> [ExperimentGenerator r]
-- ^ generators used for rendering
-> r
-- ^ the rendering backend
-> Simulation Results
-- ^ the simulation results received from the model
-> IO ()
runExperiment = runExperimentWithExecutor sequence_
-- | Run the simulation experiment in parallel.
--
-- Make sure that you compile with @-threaded@ and supply @+RTS -N2 -RTS@
-- to the generated Haskell executable on dual core processor,
-- or you won't get any parallelism. Generally, the mentioned
-- @N@ parameter should correspond to the number of cores for
-- your processor.
--
-- In case of need you might want to specify the number of
-- threads directly with help of 'experimentNumCapabilities',
-- although the real number of parallel threads can depend on many
-- factors.
runExperimentParallel :: ExperimentRendering r
=> Experiment
-- ^ the simulation experiment to run
-> [ExperimentGenerator r]
-- ^ generators used for rendering
-> r
-- ^ the rendering backend
-> Simulation Results
-- ^ the simulation results received from the model
-> IO ()
runExperimentParallel e = runExperimentWithExecutor executor e
where executor tasks =
do n <- experimentNumCapabilities e
withPool n $ \pool ->
parallel_ pool tasks
-- | Run the simulation experiment with the specified executor.
runExperimentWithExecutor :: ExperimentRendering r
=> ([IO ()] -> IO ())
-- ^ an executor that allows parallelizing the simulation if required
-> Experiment
-- ^ the simulation experiment to run
-> [ExperimentGenerator r]
-- ^ generators used for rendering
-> r
-- ^ the rendering backend
-> Simulation Results
-- ^ the simulation results received from the model
-> IO ()
runExperimentWithExecutor executor e generators r simulation =
runExperimentWriter $
do let specs = experimentSpecs e
runCount = experimentRunCount e
dirName = experimentDirectoryName e
path <- resolveFilePath "" dirName
liftIO $ do
when (experimentVerbose e) $
do putStr "Updating directory "
putStrLn path
createDirectoryIfMissing True path
reporters <- mapM (\x -> generateReporter x e r path)
generators
forM_ reporters reporterInitialise
let simulate :: Simulation ()
simulate =
do signals <- newResultPredefinedSignals
results <- simulation
let d = ExperimentData { experimentResults = experimentTransform e results,
experimentPredefinedSignals = signals }
fs <- runDynamicsInStartTime $
runEventWith EarlierEvents $
forM reporters $ \reporter ->
reporterSimulate reporter d
let m1 =
runEventInStopTime $
return ()
m2 =
runEventInStopTime $
disposeEvent $ mconcat fs
mh (SimulationException e') =
when (experimentVerbose e) $
liftIO $
do putStr "A simulation exception has been raised when running: "
putStrLn $ show e'
finallySimulation (catchSimulation m1 mh) m2
liftIO $
executor $ runSimulations simulate specs runCount
forM_ reporters reporterFinalise
renderExperiment e r reporters path
return ()