248 lines
8.2 KiB
Haskell
248 lines
8.2 KiB
Haskell
|
{-# LANGUAGE RecordWildCards #-}
|
||
|
|
||
|
module Environment where
|
||
|
|
||
|
|
||
|
import Data.Functor ((<$>))
|
||
|
import Control.Applicative ((<*>))
|
||
|
import Control.Monad (forM_)
|
||
|
import Data.List (permutations, subsequences)
|
||
|
import Numeric.LinearAlgebra
|
||
|
|
||
|
type Probability = Double
|
||
|
type Quantity = Int
|
||
|
type Activation = Double
|
||
|
type Amount = Double
|
||
|
|
||
|
-- | Nutrients are the basis for any reaction and are found in the environment of the plant.
|
||
|
data Nutrient = Sulfur
|
||
|
| Phosphor
|
||
|
| Nitrate
|
||
|
| Photosynthesis
|
||
|
deriving (Show, Enum, Bounded, Eq)
|
||
|
|
||
|
-- | Fixed, non-generic Components
|
||
|
data Component = PP
|
||
|
| FPP
|
||
|
deriving (Show, Enum, Bounded, Eq)
|
||
|
|
||
|
-- | Compounds are either direct nutrients, already processed components or GenericEnzymes
|
||
|
data Compound = Substrate Nutrient
|
||
|
| Produced Component
|
||
|
| GenericEnzyme Int
|
||
|
deriving (Show, Eq)
|
||
|
|
||
|
instance Enum Compound where
|
||
|
toEnum x
|
||
|
| x <= maxS = Substrate . toEnum $ x
|
||
|
| x - (maxS+1) <= maxP = Produced . toEnum $ x - (maxS + 1)
|
||
|
| otherwise = GenericEnzyme $ x - (maxS + 1) - (maxP + 1)
|
||
|
where
|
||
|
maxS = fromEnum (maxBound :: Nutrient)
|
||
|
maxP = fromEnum (maxBound :: Component)
|
||
|
|
||
|
fromEnum (Substrate x) = fromEnum x
|
||
|
fromEnum (Produced x) = fromEnum x + maxS + 1
|
||
|
where
|
||
|
maxS = fromEnum (maxBound :: Nutrient)
|
||
|
fromEnum (GenericEnzyme x) = x + maxS + maxP + 2
|
||
|
where
|
||
|
maxS = fromEnum (maxBound :: Nutrient)
|
||
|
maxP = fromEnum (maxBound :: Component)
|
||
|
|
||
|
|
||
|
|
||
|
-- | Enzymes are the main reaction-driver behind synthesis of intricate compounds.
|
||
|
--
|
||
|
-- They are assumed to be reversible.
|
||
|
data Enzyme = Enzyme
|
||
|
{ enzymeName :: String
|
||
|
-- ^ Name of the Enzyme.
|
||
|
, substrateRequirements :: [(Compound,Amount)]
|
||
|
-- ^ needed for reaction to take place
|
||
|
, synthesis :: ((Compound,Amount),(Compound,Amount))
|
||
|
-- ^ given x in amount -a, this will produce y in amount b
|
||
|
, dominance :: Maybe Amount
|
||
|
-- ^ in case of competition for nutrients this denotes the priority
|
||
|
-- Nothing = max possible
|
||
|
}
|
||
|
deriving (Show, Eq)
|
||
|
|
||
|
-- | conviniently make an Enzyme using 1 of the first compund to produce 1 of the second
|
||
|
makeSimpleEnzyme :: Compound -> Compound -> Enzyme
|
||
|
makeSimpleEnzyme a b = Enzyme (show a ++ " -> " ++ show b) [] ((a,-1),(b,1)) Nothing
|
||
|
|
||
|
--Example "enzymes" could be:
|
||
|
|
||
|
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)
|
||
|
|
||
|
-- Evironment
|
||
|
-- ----------
|
||
|
|
||
|
-- | In the environment we have predators that impact the fitness of our plants and
|
||
|
-- may be resistant to some compounds the plant produces. They can also differ in
|
||
|
-- their intensity.
|
||
|
data Predator = Predator { resistance :: [Compound]
|
||
|
-- ^ list of components this predator is resistant to
|
||
|
, fitnessImpact :: Amount
|
||
|
-- ^ impact on the fitness of a plant
|
||
|
-- (~ agressiveness of the herbivore)
|
||
|
} deriving (Show, Eq)
|
||
|
|
||
|
-- Exemplatory:
|
||
|
|
||
|
greenfly :: Predator -- 20% of plants die to greenfly, but the fly is
|
||
|
greenfly = Predator [Produced PP] 0.2 -- killed by any Component not being PP
|
||
|
|
||
|
-- The environment itself is just the soil and the predators. Extensions would be possible.
|
||
|
|
||
|
data Environment =
|
||
|
Environment
|
||
|
{ soil :: [(Nutrient, Amount)]
|
||
|
-- ^ soil is a list of nutrients available to the plant.
|
||
|
, predators :: [(Predator, Probability)]
|
||
|
-- ^ Predators with the probability of appearance in this generation.
|
||
|
} deriving (Show, Eq)
|
||
|
|
||
|
-- Example:
|
||
|
|
||
|
exampleEnvironment :: Environment
|
||
|
exampleEnvironment =
|
||
|
Environment
|
||
|
{ soil = [ (Nitrate, 2)
|
||
|
, (Phosphor, 3)
|
||
|
, (Photosynthesis, 10)
|
||
|
]
|
||
|
, predators = [ (greenfly, 0.1) ]
|
||
|
}
|
||
|
|
||
|
-- Plants
|
||
|
-- ------
|
||
|
|
||
|
-- Plants consist of a Genome responsible for creation of the PSM and also an
|
||
|
-- external state how many nutrients and compounds are currently inside the plant.
|
||
|
|
||
|
type Genome = [(Enzyme, Quantity, Activation)]
|
||
|
|
||
|
data Plant = Plant
|
||
|
{ genome :: Genome
|
||
|
-- ^ the genetic characteristic of the plant
|
||
|
, absorbNutrients :: Environment -> [(Nutrient,Amount)]
|
||
|
-- ^ the capability to absorb nutrients given an environment
|
||
|
}
|
||
|
instance Show Plant where
|
||
|
show p = "Plant with Genome " ++ show (genome p)
|
||
|
instance Eq Plant where
|
||
|
a == b = genome a == genome b
|
||
|
|
||
|
-- | The following example yields in the example-environment this population:
|
||
|
--
|
||
|
-- >>> printPopulation [pps, fpps] plants
|
||
|
-- Population:
|
||
|
-- PPS ______oöö+++______oöö+++____________oöö+++oöö+++
|
||
|
-- FPPS ____________oöö+++oöö+++______oöö+++______oöö+++
|
||
|
plants :: [Plant]
|
||
|
plants = (\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 (Environment s _) = limit Phosphor 2
|
||
|
. limit Nitrate 1
|
||
|
. limit Sulfur 0
|
||
|
<$> s
|
||
|
-- 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')
|
||
|
|
||
|
-- Fitness
|
||
|
-- -------
|
||
|
|
||
|
-- The fitness-measure is central for the generation of offspring and the
|
||
|
-- simulation. It evaluates the probability for passing on genes given a plant in
|
||
|
-- an environment.
|
||
|
|
||
|
type Fitness = Double
|
||
|
|
||
|
fitness :: Environment -> Plant -> Fitness
|
||
|
fitness e p = survivalRate
|
||
|
where
|
||
|
nutrients = absorbNutrients p e
|
||
|
products = produceCompounds p nutrients
|
||
|
survivalRate = deterPredators (predators e) products
|
||
|
|
||
|
|
||
|
produceCompounds :: Plant -> [(Nutrient, Amount)] -> Vector Amount
|
||
|
produceCompounds (Plant genes _) substrate = final
|
||
|
where
|
||
|
initialAmount = assoc 10 0 ((\(n,a) -> (fromEnum $ Substrate n,a)) <$> substrate)
|
||
|
enzymes = (\(e,q,a) -> (synthesis e,(fromIntegral q)*a)) <$> genes -- [(((Component,Amount),(Component,Amount)),q*a)], Amount got * by quantity & activation
|
||
|
positions = concat $ (\(((i,ia),(o,oa)),f) -> [((fromEnum i,fromEnum i),ia),((fromEnum o,fromEnum i),oa)]) <$> enzymes -- [((row,column),amount)]
|
||
|
mat = accum (konst 0 (10::Int,10::Int)) (+) positions --accumulate all entries into one matrix.
|
||
|
(l,v) = eig ((*0.01) `cmap` mat)
|
||
|
final = (realPart `cmap` (v <> ((^100) `cmap` diag l) <> (tr v))) #> initialAmount
|
||
|
|
||
|
|
||
|
deterPredators :: [(Predator, Probability)] -> Vector Amount -> Probability
|
||
|
deterPredators ps cs' = sum $ do
|
||
|
(c,a) <- cs -- for every compound
|
||
|
(p,prob) <- ps -- and every predator
|
||
|
return (if c `elem` (resistance p) -- if the plant cannot deter the predator
|
||
|
then prob * fitnessImpact p -- impact it weighted by probability
|
||
|
else 0)
|
||
|
where
|
||
|
cs = (zip (toEnum <$> [1..]) (toList cs')) :: [(Compound,Amount)]
|
||
|
|
||
|
|
||
|
-- Mating & Creation of diversity
|
||
|
-- ------------------------------
|
||
|
|
||
|
-- Running the simulation
|
||
|
-- ----------------------
|
||
|
|
||
|
main = do
|
||
|
putStrLn "Environment:"
|
||
|
print exampleEnvironment
|
||
|
putStrLn "Example population:"
|
||
|
printPopulation [pps, fpps] plants
|
||
|
|
||
|
-- Utility Functions
|
||
|
-- -----------------
|
||
|
|
||
|
getAmountOf :: Compound -> [(Compound, Amount)] -> Amount
|
||
|
getAmountOf c = sum . fmap snd . filter ((== c) . fst)
|
||
|
|
||
|
printPopulation :: [Enzyme] -> [Plant] -> IO ()
|
||
|
printPopulation es ps = do
|
||
|
let padded i str = take i $ str ++ repeat ' '
|
||
|
putStrLn "Population:"
|
||
|
forM_ es $ \e -> do
|
||
|
putStr $ padded 8 (show 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 ""
|