creatur 5.0.1 → 5.2.0
raw patch · 8 files changed
+438/−74 lines, 8 files
Files
- creatur.cabal +4/−2
- src/ALife/Creatur/Genetics/Analysis.hs +1/−1
- src/ALife/Creatur/Genetics/BRGCWord16.hs +287/−0
- src/ALife/Creatur/Genetics/Reproduction/Asexual.hs +0/−66
- src/ALife/Creatur/Genetics/Reproduction/Sexual.hs +4/−4
- src/ALife/Creatur/Genetics/Reproduction/SimplifiedSexual.hs +66/−0
- test/ALife/Creatur/Genetics/BRGCWord16QC.hs +73/−0
- test/Main.hs +3/−1
creatur.cabal view
@@ -1,5 +1,5 @@ Name: creatur-Version: 5.0.1+Version: 5.2.0 Stability: experimental Synopsis: Framework for artificial life experiments. Description: A software framework for automating experiments@@ -52,10 +52,11 @@ ALife.Creatur.Genetics.Code, ALife.Creatur.Genetics.BRGCBool, ALife.Creatur.Genetics.BRGCWord8,+ ALife.Creatur.Genetics.BRGCWord16, ALife.Creatur.Genetics.Diploid, ALife.Creatur.Genetics.Recombination,- ALife.Creatur.Genetics.Reproduction.Asexual, ALife.Creatur.Genetics.Reproduction.Sexual,+ ALife.Creatur.Genetics.Reproduction.SimplifiedSexual, ALife.Creatur.Logger, ALife.Creatur.Universe, ALife.Creatur.Task,@@ -112,4 +113,5 @@ ALife.Creatur.Genetics.DiploidQC, ALife.Creatur.Genetics.BRGCBoolQC ALife.Creatur.Genetics.BRGCWord8QC+ ALife.Creatur.Genetics.BRGCWord16QC ALife.Creatur.Genetics.RecombinationQC
src/ALife/Creatur/Genetics/Analysis.hs view
@@ -21,7 +21,7 @@ import GHC.Generics class Analysable g where- -- | Writes a gene to a sequence.+ -- | Analyses a genetic sequence. analyse :: g -> String default analyse :: (Generic g, GAnalysable (Rep g)) => g -> String
+ src/ALife/Creatur/Genetics/BRGCWord16.hs view
@@ -0,0 +1,287 @@+------------------------------------------------------------------------+-- |+-- Module : ALife.Creatur.Genetics.BRGCWord16+-- Copyright : (c) Amy de Buitléir 2013+-- License : BSD-style+-- Maintainer : amy@nualeargais.ie+-- Stability : experimental+-- Portability : portable+--+-- Utilities for working with genes that are encoded as a sequence of+-- 16-bit words, using a Binary Reflected Gray Code (BRGC).+--+-- A Gray code maps values to codes in a way that guarantees that the+-- codes for two consecutive values will differ by only one bit. This+-- feature can be useful in evolutionary programming because the genes+-- resulting from a crossover operation are likely to be similar to+-- the inputs. This helps to ensure that offspring are similar to+-- their parents, as any radical changes from one generation to the+-- next are the result of mutation alone.+--+------------------------------------------------------------------------+{-# LANGUAGE TypeFamilies, FlexibleContexts, FlexibleInstances,+ DefaultSignatures, DeriveGeneric, TypeOperators #-}+module ALife.Creatur.Genetics.BRGCWord16+ (+ Genetic(..),+ Sequence,+ Writer,+ write,+ runWriter,+ Reader,+ read,+ runReader,+ copy,+ consumed,+ DiploidSequence,+ DiploidReader,+ readAndExpress,+ runDiploidReader,+ getAndExpress,+ getAndExpressWithDefault,+ copy2,+ consumed2,+ putRawWord16,+ getRawWord16,+ putRawWord16s,+ getRawWord16s+ ) where++import Prelude hiding (read)+import ALife.Creatur.Genetics.Diploid (Diploid, express)+import ALife.Creatur.Util (fromEither)+import Codec.Gray (integralToGray, grayToIntegral)+import Control.Applicative ((<$>), (<*>))+import Control.Monad.State.Lazy (StateT, runState, execState, evalState)+import qualified Control.Monad.State.Lazy as S (put, get, gets)+import Data.Char (ord, chr)+import Data.Functor.Identity (Identity)+import Data.Word (Word8, Word16)+import GHC.Generics++type Sequence = [Word16]++type Writer = StateT Sequence Identity++write :: Genetic x => x -> Sequence+write x = execState (put x) []++runWriter :: Writer () -> Sequence+runWriter w = execState w []++type Reader = StateT (Sequence, Int) Identity++read :: Genetic g => Sequence -> Either [String] g+read s = evalState get (s, 0)++runReader :: Reader g -> Sequence -> g+runReader r s = evalState r (s, 0)++-- | Return the entire genome.+copy :: Reader Sequence+copy = S.gets fst++-- | Return the portion of the genome that has been read.+consumed :: Reader Sequence+consumed = do+ (xs, i) <- S.get+ return $ take i xs++-- | A class representing anything which is represented in, and+-- determined by, an agent's genome.+-- This might include traits, parameters, "organs" (components of+-- agents), or even entire agents.+-- Instances of this class can be thought of as genes, i.e.,+-- instructions for building an agent.+class Genetic g where+ -- | Writes a gene to a sequence.+ put :: g -> Writer ()++ default put :: (Generic g, GGenetic (Rep g)) => g -> Writer ()+ put = gput . from++ -- | Reads the next gene in a sequence.+ get :: Reader (Either [String] g)++ default get :: (Generic g, GGenetic (Rep g)) => Reader (Either [String] g)+ get = do+ a <- gget+ return $ fmap to a++ getWithDefault :: g -> Reader g+ getWithDefault d = fmap (fromEither d) get++class GGenetic f where+ gput :: f a -> Writer ()+ gget :: Reader (Either [String] (f a))++-- | Unit: used for constructors without arguments+instance GGenetic U1 where+ gput U1 = return ()+ gget = return (Right U1)++-- | Constants, additional parameters and recursion of kind *+instance (GGenetic a, GGenetic b) => GGenetic (a :*: b) where+ gput (a :*: b) = gput a >> gput b+ gget = do+ a <- gget+ b <- gget+ return $ (:*:) <$> a <*> b++-- | Meta-information (constructor names, etc.)+instance (GGenetic a, GGenetic b) => GGenetic (a :+: b) where+ gput (L1 x) = putRawWord16 0 >> gput x+ gput (R1 x) = putRawWord16 1 >> gput x+ gget = do+ a <- getRawWord16+ case a of+ Right x -> do+ if even x -- Only care about the last bit+ then fmap (fmap L1) gget+ else fmap (fmap R1) gget+ Left s -> return $ Left s++-- | Sums: encode choice between constructors+instance (GGenetic a) => GGenetic (M1 i c a) where+ gput (M1 x) = gput x+ gget = fmap (fmap M1) gget++-- | Products: encode multiple arguments to constructors+instance (Genetic a) => GGenetic (K1 i a) where+ gput (K1 x) = put x+ gget = do+ a <- get+ return $ fmap K1 a++--+-- Instances+--++instance Genetic Bool where+ put False = putRawWord16 0+ put True = putRawWord16 1+ get = fmap (fmap word16ToBool) getRawWord16++word16ToBool :: Word16 -> Bool+word16ToBool x = if even x then False else True++instance Genetic Char where+ put = putRawWord16 . fromIntegral . ord+ get = fmap (fmap (chr . fromIntegral)) getRawWord16++instance Genetic Word8 where+ put x = put (fromIntegral x :: Word16)+ get = do+ x <- get :: Reader (Either [String] Word16)+ return $ fmap fromIntegral x++instance Genetic Word16 where+ put = putRawWord16 . integralToGray+ get = fmap (fmap grayToIntegral) getRawWord16++instance (Genetic a) => Genetic [a]++instance (Genetic a) => Genetic (Maybe a)++instance (Genetic a, Genetic b) => Genetic (a, b)++instance (Genetic a, Genetic b) => Genetic (Either a b)+++--+-- Utilities+--++-- | Write a Word16 value to the genome without encoding it+putRawWord16 :: Word16 -> Writer ()+putRawWord16 x = do+ xs <- S.get+ S.put (xs ++ [x])++-- | Read a Word16 value from the genome without decoding it+getRawWord16 :: Reader (Either [String] Word16)+getRawWord16 = do+ (xs, i) <- S.get+ let xs' = drop i xs+ if null xs'+ then return $ Left ["End of sequence"]+ else do+ let x = head xs'+ S.put (xs, i+1)+ return $ Right x++-- | Write a raw sequence of Word16 values to the genome+putRawWord16s :: [Word16] -> Writer ()+putRawWord16s ys = do+ xs <- S.get+ S.put (xs ++ ys)++-- | Read a raw sequence of Word16 values from the genome+getRawWord16s :: Int -> Reader (Either [String] [Word16])+getRawWord16s n =+ if n == 0+ then return $ Right []+ else do+ (xs, i) <- S.get+ let xs' = drop i xs+ if null xs' || length xs' < n+ then return $ Left ["End of genes"]+ else do+ let ys = take n xs'+ S.put (xs, i+n)+ return $ Right ys++--+-- Diploid genes+--++type DiploidSequence = (Sequence, Sequence)++type DiploidReader = StateT ((Sequence, Int), (Sequence, Int)) Identity++readAndExpress :: (Genetic g, Diploid g) => DiploidSequence -> Either [String] g+readAndExpress (s1, s2) = evalState getAndExpress ((s1, 0), (s2, 0))++runDiploidReader :: DiploidReader g -> DiploidSequence -> g+runDiploidReader r (s1, s2) = evalState r ((s1, 0), (s2, 0))++-- | Return the entire genome.+copy2 :: DiploidReader DiploidSequence+copy2 = do+ (ra, rb) <- S.get+ let as = evalState copy ra+ let bs = evalState copy rb+ return (as, bs)++-- | Return the portion of the genome that has been read.+consumed2 :: DiploidReader DiploidSequence+consumed2 = do+ (ra, rb) <- S.get+ let as = evalState consumed ra+ let bs = evalState consumed rb+ return (as, bs)++-- | Read the next pair of genes from twin sequences of genetic+-- information, and return the resulting gene (after taking+-- into account any dominance relationship) and the remaining+-- (unread) portion of the two nucleotide strands.+getAndExpress :: (Genetic g, Diploid g) => DiploidReader (Either [String] g)+getAndExpress = do+ (sa, sb) <- S.get+ let (a, sa') = runState get sa+ let (b, sb') = runState get sb+ S.put (sa', sb')+ return $ expressEither a b++getAndExpressWithDefault :: (Genetic g, Diploid g) => g -> DiploidReader g+getAndExpressWithDefault d = fmap (fromEither d) getAndExpress++expressEither+ :: Diploid g+ => Either [String] g -> Either [String] g+ -> Either [String] g+expressEither (Right a) (Right b) = Right (express a b)+expressEither (Right a) (Left _) = Right a+expressEither (Left _) (Right b) = Right b+expressEither (Left xs) (Left ys) =+ Left $ (map ("sequence 1: " ++) xs) ++ (map ("sequence 2: " ++) ys)
− src/ALife/Creatur/Genetics/Reproduction/Asexual.hs
@@ -1,66 +0,0 @@---------------------------------------------------------------------------- |--- Module : ALife.Creatur.Genetics.Reproduction.Asexual--- Copyright : (c) Amy de Buitléir 2012-2013--- License : BSD-style--- Maintainer : amy@nualeargais.ie--- Stability : experimental--- Portability : portable------ A reproduction method for artificial lifeforms where:------ * Each agent has a /single/ strand of genetic information.------ * Each child has two parents.------ * Each parent contributes approximately half of its genetic--- information to the offspring.-----------------------------------------------------------------------------{-# LANGUAGE TypeFamilies #-}-module ALife.Creatur.Genetics.Reproduction.Asexual- (- Reproductive(..)- ) where--import ALife.Creatur (AgentId)-import Control.Monad.Random (Rand, RandomGen)---- | A species that reproduces, transmitting genetic information to--- its offspring. Minimal complete definition: all except @mate@.-class Reproductive a where-- -- | A sequence of hereditary information for an agent.- type Base a-- -- | Recombines the genetic information from two parents, creating- -- genetic information for potential offspring.- --- -- Typically this involves the following steps:- --- -- 1. Recombine the two strands of genetic information (one from- -- each parent) to obtain two new strands.- --- -- 1. Discard one strand, and return the remaining one.- recombine :: RandomGen r => a -> a -> Rand r (Base a)-- -- | Builds an agent based on the genome provided, if it is possible- -- to do so.- build :: AgentId -> Base a -> Either [String] a-- -- | @'makeOffspring' (parent1, parent2) name@ uses the genetic- -- information from @parent1@ and @parent2@ to produce a child with- -- the agent ID @name@. The default implementation:- --- -- 1. Calls @'recombine'@ to create a genome for the child.- --- -- 2. Calls @'build'@ to construct a child with this genome.- makeOffspring- :: RandomGen r- => a -> a -> AgentId -> Rand r (Either [String] a)- makeOffspring a b name = do- g <- recombine a b- return $ build name g---
src/ALife/Creatur/Genetics/Reproduction/Sexual.hs view
@@ -32,18 +32,18 @@ -- | A sequence of hereditary information for an agent. -- The type signature for the agent's genome is - -- (Base a, Base a).- type Base a+ -- (Strand a, Strand a).+ type Strand a -- | From the /two/ strands of the genetic information from this -- agent, creates a /single/ strand that will contribute to the -- child's genome. -- (This is analogous to creating either a single sperm or ova.)- produceGamete :: RandomGen r => a -> Rand r (Base a)+ produceGamete :: RandomGen r => a -> Rand r (Strand a) -- | Builds an agent based on the genome provided, if it is possible -- to do so.- build :: AgentId -> (Base a, Base a) -> Either [String] a+ build :: AgentId -> (Strand a, Strand a) -> Either [String] a -- | @'makeOffspring' (parent1, parent2) name@ uses the genetic -- information from @parent1@ and @parent2@ to produce a child with
+ src/ALife/Creatur/Genetics/Reproduction/SimplifiedSexual.hs view
@@ -0,0 +1,66 @@+------------------------------------------------------------------------+-- |+-- Module : ALife.Creatur.Genetics.Reproduction.SimplifiedSexual+-- Copyright : (c) Amy de Buitléir 2012-2013+-- License : BSD-style+-- Maintainer : amy@nualeargais.ie+-- Stability : experimental+-- Portability : portable+--+-- A reproduction method for artificial lifeforms where:+--+-- * Each agent has a /single/ strand of genetic information.+--+-- * Each child has two parents.+--+-- * Each parent contributes approximately half of its genetic+-- information to the offspring.+--+------------------------------------------------------------------------+{-# LANGUAGE TypeFamilies #-}+module ALife.Creatur.Genetics.Reproduction.SimplifiedSexual+ (+ Reproductive(..)+ ) where++import ALife.Creatur (AgentId)+import Control.Monad.Random (Rand, RandomGen)++-- | A species that reproduces, transmitting genetic information to+-- its offspring. Minimal complete definition: all except @mate@.+class Reproductive a where++ -- | A sequence of hereditary information for an agent.+ type Strand a++ -- | Recombines the genetic information from two parents, creating+ -- genetic information for potential offspring.+ --+ -- Typically this involves the following steps:+ --+ -- 1. Recombine the two strands of genetic information (one from+ -- each parent) to obtain two new strands.+ --+ -- 1. Discard one strand, and return the remaining one.+ recombine :: RandomGen r => a -> a -> Rand r (Strand a)++ -- | Builds an agent based on the genome provided, if it is possible+ -- to do so.+ build :: AgentId -> Strand a -> Either [String] a++ -- | @'makeOffspring' (parent1, parent2) name@ uses the genetic+ -- information from @parent1@ and @parent2@ to produce a child with+ -- the agent ID @name@. The default implementation:+ --+ -- 1. Calls @'recombine'@ to create a genome for the child.+ --+ -- 2. Calls @'build'@ to construct a child with this genome.+ makeOffspring+ :: RandomGen r+ => a -> a -> AgentId -> Rand r (Either [String] a)+ makeOffspring a b name = do+ g <- recombine a b+ return $ build name g+++
+ test/ALife/Creatur/Genetics/BRGCWord16QC.hs view
@@ -0,0 +1,73 @@+------------------------------------------------------------------------+-- |+-- Module : ALife.Creatur.Genetics.BRGCWord16QC+-- Copyright : (c) Amy de Buitléir 2013+-- License : BSD-style+-- Maintainer : amy@nualeargais.ie+-- Stability : experimental+-- Portability : portable+--+-- QuickCheck tests.+--+------------------------------------------------------------------------+{-# LANGUAGE DeriveGeneric, FlexibleInstances #-}+module ALife.Creatur.Genetics.BRGCWord16QC+ (+ test+ ) where++import Prelude hiding (read)+import ALife.Creatur.Genetics.BRGCWord16+import ALife.Creatur.Genetics.Analysis (Analysable)+import Control.Applicative ((<$>), (<*>))+import Data.Word (Word8, Word16)+import GHC.Generics (Generic)+import Test.Framework as TF (Test, testGroup)+import Test.Framework.Providers.QuickCheck2 (testProperty)+import Test.QuickCheck (Arbitrary, Gen, Property, arbitrary, choose,+ oneof, property, sized, vectorOf)++prop_round_trippable :: (Eq g, Genetic g) => g -> Property+prop_round_trippable g = property $ g' == Right g+ where x = write g+ g' = read x++data TestStructure = A | B Bool | C Word16 | D Word8 Char | E [TestStructure]+ deriving (Show, Eq, Generic)++instance Genetic TestStructure+instance Analysable TestStructure++sizedArbTestStructure :: Int -> Gen TestStructure+sizedArbTestStructure 0 =+ oneof [ return A, B <$> arbitrary, C <$> arbitrary,+ D <$> arbitrary <*> arbitrary]+sizedArbTestStructure n = do+ k <- choose (0,min 8 (n-1))+ oneof [+ return A,+ B <$> arbitrary,+ C <$> arbitrary,+ D <$> arbitrary <*> arbitrary,+ E <$> vectorOf k (sizedArbTestStructure (n-1))+ ]+ +instance Arbitrary TestStructure where+ arbitrary = sized sizedArbTestStructure++test :: Test+test = testGroup "ALife.Creatur.Genetics.BRGCWord16QC"+ [+ testProperty "prop_round_trippable - Bool"+ (prop_round_trippable :: Bool -> Property),+ testProperty "prop_round_trippable - Char"+ (prop_round_trippable :: Char -> Property),+ testProperty "prop_round_trippable - Word8"+ (prop_round_trippable :: Word8 -> Property),+ testProperty "prop_round_trippable - Word16"+ (prop_round_trippable :: Word16 -> Property),+ testProperty "prop_round_trippable - TestStructure"+ (prop_round_trippable :: TestStructure -> Property)+ ]++
test/Main.hs view
@@ -22,6 +22,7 @@ import ALife.Creatur.Genetics.RecombinationQC (test) import ALife.Creatur.Genetics.BRGCBoolQC (test) import ALife.Creatur.Genetics.BRGCWord8QC (test)+import ALife.Creatur.Genetics.BRGCWord16QC (test) import Test.Framework as TF (defaultMain, Test) @@ -37,7 +38,8 @@ ALife.Creatur.Genetics.DiploidQC.test, ALife.Creatur.Genetics.RecombinationQC.test, ALife.Creatur.Genetics.BRGCBoolQC.test,- ALife.Creatur.Genetics.BRGCWord8QC.test+ ALife.Creatur.Genetics.BRGCWord8QC.test,+ ALife.Creatur.Genetics.BRGCWord16QC.test ] main :: IO ()