209 lines
8.0 KiB
Haskell
209 lines
8.0 KiB
Haskell
{-# LANGUAGE BangPatterns #-}
|
|
module Main where
|
|
|
|
import Text.Printf
|
|
import Control.Monad.Reader
|
|
import Numeric.LinearAlgebra
|
|
import Data.List
|
|
import System.Random
|
|
import Control.Concurrent
|
|
import Control.Parallel.Strategies
|
|
import qualified Debug.Trace as Debug
|
|
import System.IO
|
|
|
|
import ArbitraryEnzymeTree
|
|
import Environment
|
|
|
|
-- Example definitions
|
|
-- -------------------
|
|
|
|
-- Enzymes
|
|
|
|
pps :: Enzyme -- uses Phosphor from Substrate to produce PP
|
|
pps = Enzyme "PPS" [(Substrate Phosphor,1)] ((Substrate Phosphor,-1),(Produced PP,1)) Nothing
|
|
|
|
fpps :: Enzyme -- PP -> FPP
|
|
fpps = makeSimpleEnzyme (Produced PP) (Produced FPP)
|
|
|
|
-- Predator
|
|
|
|
greenfly :: Predator -- 20% of plants die to greenfly, but the fly is
|
|
greenfly = Predator [] 0.2 -- killed by any toxic Component
|
|
|
|
-- Environment
|
|
|
|
exampleEnvironment :: Int -> [Enzyme] -> [(Predator,Probability)] -> [(Compound,Amount)] -> Environment
|
|
exampleEnvironment addedC es pred tox =
|
|
Environment
|
|
{ soil = [ (Nitrate, 2)
|
|
, (Phosphor, 3)
|
|
, (Photosynthesis, 10)
|
|
]
|
|
, predators = pred -- [ (greenfly, 0.1) ]
|
|
, metabolismIteration = 100
|
|
, maxCompound = maxCompoundWithoutGeneric + addedC
|
|
, toxicCompounds = tox --[(Produced FPP,0.1)] ++ tox
|
|
, possibleEnzymes = es -- [pps,fpps] ++ es
|
|
}
|
|
|
|
-- Plants
|
|
|
|
examplePlants :: [Plant]
|
|
examplePlants = (\g -> Plant g defaultAbsorption) <$> genomes
|
|
where
|
|
enzymes = [pps, fpps]
|
|
quantity = [1,2] :: [Quantity]
|
|
activation = [0.7, 0.9, 1]
|
|
|
|
genomes = do
|
|
e <- permutations enzymes
|
|
e' <- subsequences e
|
|
q <- quantity
|
|
a <- activation
|
|
return $ (,,) <$> e' <*> [q] <*> [a]
|
|
|
|
defaultAbsorption = fmap ( limit Phosphor 2
|
|
. limit Nitrate 1
|
|
. limit Sulfur 0
|
|
) <$> asks soil
|
|
-- custom absorbtion with helper-function:
|
|
limit :: Nutrient -> Amount -> (Nutrient, Amount) -> (Nutrient, Amount)
|
|
limit n a (n', a')
|
|
| n == n' = (n, min a a') -- if we should limit, then we do ;)
|
|
| otherwise = (n', a')
|
|
|
|
-- Running the simulation
|
|
-- ----------------------
|
|
|
|
loop :: Int -> [Plant] -> Environment -> IO ()
|
|
loop loopAmount = loop' loopAmount 0
|
|
|
|
where
|
|
loop' :: Int -> Int -> [Plant] -> Environment -> IO ()
|
|
loop' loopAmount curLoop plants e = unless (loopAmount == curLoop) $ do
|
|
putStr "\ESC[2J\ESC[H"
|
|
printEnvironment e
|
|
putStrLn ""
|
|
putStrLn $ "Generation " ++ show curLoop ++ " of " ++ show loopAmount ++ ":"
|
|
newPlants <- flip runReaderT e $ do
|
|
! fs <- sequence (fitness <$> plants)
|
|
let fps = zip plants fs -- gives us plants & their fitness in a tuple
|
|
sumFitness = sum fs
|
|
pe <- asks possibleEnzymes
|
|
tc <- asks toxicCompounds
|
|
liftIO $ printPopulation tc pe fps
|
|
liftIO $ hFlush stdout
|
|
-- generate 100 new plants.
|
|
sequence . flip fmap [1..100] $ \_ -> do
|
|
parent' <- liftIO $ randomRIO (0,sumFitness)
|
|
let
|
|
-- if we only have one parent in our list, take it.
|
|
findParent :: Double -> [(Plant,Double)] -> Plant
|
|
findParent _ [(last,_)] = last
|
|
-- otherwise count down x to find the parent in the list
|
|
findParent x ((p,f):ps)
|
|
| x < f = p
|
|
| otherwise = findParent (x-f) ps
|
|
parent = findParent parent' fps
|
|
haploMate parent
|
|
hFlush stdout
|
|
threadDelay $ 100*1000 -- sleep 100ms
|
|
loop' loopAmount (curLoop+1) newPlants e
|
|
|
|
main :: IO ()
|
|
main = do
|
|
hSetBuffering stdin NoBuffering
|
|
hSetBuffering stdout NoBuffering
|
|
randomCompounds <- makeHead (Substrate Photosynthesis) <$> generateTreeFromList 40 (toEnum <$> [(maxCompoundWithoutGeneric+1)..] :: [Compound]) -- generate roughly x compounds
|
|
ds <- randoms <$> newStdGen
|
|
probs <- randomRs (0.2,0.7) <$> newStdGen
|
|
let emptyPlants = replicate 100 emptyPlant
|
|
poisonedTree = poisonTree ds randomCompounds
|
|
poisonCompounds = foldMap (\(a,b) -> if a > 0.5 then [(b,a)] else []) $ poisonedTree
|
|
predators <- generatePredators 0.5 poisonedTree
|
|
let env = exampleEnvironment (getTreeSize randomCompounds) (generateEnzymeFromTree randomCompounds) (zip predators probs) poisonCompounds
|
|
printEnvironment env
|
|
writeFile "poison.twopi" $ generateDotFromPoisonTree "poison" 0.5 $ poisonedTree
|
|
putStr "\ESC[?1049h"
|
|
loop 200 emptyPlants env
|
|
putStrLn "Simulation ended. Press key to exit."
|
|
_ <- getChar
|
|
putStr "\ESC[?1049l"
|
|
|
|
-- fitness <- runReaderT (sequence $ (\a -> do p <- absorbNutrients a >>= produceCompounds a; (,,) a p <$> deterPredators p) <$> emptyPlants) exampleEnvironment
|
|
-- mapM_ (printf "%15.15s, " . show . toEnum @Compound) [0..maxCompoundWithoutGeneric]
|
|
-- putStrLn "Fitness"
|
|
-- forM_ fitness $ \(p, c, f) -> do
|
|
-- mapM_ (printf "%15.2f, ") (toList c)
|
|
-- printf "%15.2f" f
|
|
-- putStr "\n"
|
|
|
|
generatePredators :: Double -> EnzymeTree s (Double,Compound) -> IO [Predator]
|
|
generatePredators threshold t = do
|
|
ps <- mapM generatePredators' $ getSubTrees t
|
|
return $ filter ((/= []) . irresistance) $ concat ps -- filter out predators that are resistant to everything because this does not make sense in our model.
|
|
where
|
|
generatePredators' :: (EnzymeTree s (Double, Compound)) -> IO [Predator]
|
|
generatePredators' t = do -- not fully resistant to t, but fully resistant to everything in ts
|
|
let comps = foldMap (\(a,b) -> if a > threshold then [(a,b)] else []) t
|
|
amount <- randomRIO (0,length comps + 1) :: IO Int
|
|
forM [1..amount] $ \_ -> do
|
|
impact <- randomRIO (0.2,0.7)
|
|
rands <- randoms <$> newStdGen
|
|
let unresists = foldMap (\((a,b),r) -> if r*2 < a then [b] else []) $ zip comps rands
|
|
return $ Predator unresists impact
|
|
|
|
printEnvironment :: Environment -> IO ()
|
|
printEnvironment (Environment soil pred metaIter maxComp toxic possEnz) =
|
|
do
|
|
putStrLn "Environment:"
|
|
putStrLn $ "Soil: " ++ show soil
|
|
putStrLn $ "Predators: " ++ show pred
|
|
putStrLn $ "PSM Iters: " ++ show metaIter
|
|
putStrLn $ "Compounds: " ++ show ((toEnum <$> [0..maxComp]) :: [Compound])
|
|
putStrLn $ "Toxic: " ++ show toxic
|
|
|
|
printPopulation :: [(Compound, Amount)] -> [Enzyme] -> [(Plant,Double)] -> IO ()
|
|
printPopulation toxins es ps = do
|
|
let padded i str = take i $ str ++ repeat ' '
|
|
putStr $ padded 50 "Population:"
|
|
forM_ ps $ \(_,f) -> putStr (printColor f '█')
|
|
putStrLn colorOff
|
|
forM_ es $ \e -> do
|
|
putStr $ case Data.List.find (\(t,_) -> (t==) . fst . snd . synthesis $ e) toxins of
|
|
Just (_,toxicity) -> "\ESC[38;5;" ++ show (16 + 36*5 + 6*floor (5*(1-toxicity)) + 0) ++ "m" -- yellow -> red rainbow for tocixity 0 -> 1
|
|
++ padded 50 (show (enzymeName e)) ++ "\ESC[0m"
|
|
Nothing -> padded 50 (show (enzymeName e))
|
|
forM_ ps $ \(Plant g _,_) -> do
|
|
let curE = sum $ map (\(_,q,a) -> fromIntegral q*a)
|
|
. filter (\(e',_,_) -> e == e')
|
|
$ g
|
|
plot x
|
|
| x > 2 = "O"
|
|
| x > 1 = "+"
|
|
| x > 0.7 = "ö"
|
|
| x > 0.5 = "o"
|
|
| x > 0 = "."
|
|
| otherwise = "_"
|
|
putStr (plot curE)
|
|
putStrLn ""
|
|
|
|
printColor :: Double -> Char -> String
|
|
printColor x c
|
|
| x*x < 0.5 = "\ESC[38;5;" ++ show (16 + 36*5 + 6*floor (5*2*x') + 0) ++ "m" ++ [c] ++ ""
|
|
| otherwise = "\ESC[38;5;" ++ show (16 + 36*floor (5*2*(1-x')) + 6*5 + 0) ++ "m" ++ [c] ++ ""
|
|
-- 32 bit
|
|
-- | x*x < 0.5 = "\ESC[38;2;255;" ++ (show . floor $ 255*2*x') ++ ";0m" ++ [c] ++ ""
|
|
-- | otherwise = "\ESC[38;2;" ++ (show . floor $ 255*2*(1-x')) ++ ";255;0m" ++ [c] ++ ""
|
|
where x' = x*x
|
|
|
|
colorOff :: String
|
|
colorOff = "\ESC[0m"
|
|
|
|
generateEnzymeFromTree :: EnzymeTree s Compound -> [Enzyme]
|
|
generateEnzymeFromTree t = (makeSimpleEnzyme c . getElement <$> sts)
|
|
++ concatMap generateEnzymeFromTree sts
|
|
where
|
|
c = getElement t
|
|
sts = getSubTrees t
|