packages feed

creatur (empty) → 2.0.10

raw patch · 25 files changed

+1827/−0 lines, 25 filesdep +HUnitdep +MonadRandomdep +QuickChecksetup-changed

Dependencies added: HUnit, MonadRandom, QuickCheck, array, base, base-unicode-symbols, binary, bytestring, cereal, creatur, directory, ghc-prim, gray-extended, hdaemonize, hmatrix, lens, mtl, old-locale, process, random, split, temporary, test-framework, test-framework-hunit, test-framework-quickcheck2, time, transformers, unix, zlib

Files

+ LICENSE view
@@ -0,0 +1,27 @@+Copyright (c) 2010-2012, Amy de Buitléir+All rights reserved.++Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met:++* Redistributions of source code must retain the above copyright +  notice, this list of conditions and the following disclaimer.+* Redistributions in binary form must reproduce the above copyright+  notice, this list of conditions and the following disclaimer in the+  documentation and/or other materials provided with the distribution.+* Neither the name of the author nor the names of other contributors+  may be used to endorse or promote products derived from this software+  without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ creatur.cabal view
@@ -0,0 +1,98 @@+Name:              creatur+Version:           2.0.10+Stability:         experimental+Synopsis:          Framework for artificial life experiments.+Description:       A software framework for automating experiments+                   with artificial life. It provides a daemon which+                   maintains its own "clock", schedules events, +                   provides logging, and ensures that each agent gets +                   its turn to use the CPU. You can use other +                   applications on the computer at the same time+                   without fear of interfering with experiments; they+                   will run normally, just more slowly. See the +                   tutorial at +                   <https://github.com/mhwombat/creatur-examples/raw/master/Tutorial.pdf>+                   for examples on how to use the+                   Créatúr framework.+                   .+                   About the name: \"Créatúr\" (pronounced kray-toor)+                   is an irish word meaning animal, creature, or an +                   unfortunate person.+Category:          AI+License:           BSD3+License-file:      LICENSE+Copyright:         (c) Amy de Buitléir 2010-2012+Author:            Amy de Buitléir+Maintainer:        amy@nualeargais.ie+Build-Type:        Simple+Cabal-Version:     >=1.8++library+  GHC-Options:      -Wall+  Hs-source-dirs:   src+  exposed-modules:  ALife.Creatur,+                    ALife.Creatur.AgentNamer,+                    ALife.Creatur.Clock,+                    ALife.Creatur.Counter,+                    ALife.Creatur.Daemon,+                    ALife.Creatur.Database,+                    ALife.Creatur.Database.FileSystem,+                    ALife.Creatur.Genetics.Code,+                    ALife.Creatur.Genetics.CodeInternal,+                    ALife.Creatur.Genetics.Gene,+                    ALife.Creatur.Genetics.Recombination,+                    ALife.Creatur.Genetics.Reproduction.Asexual,+                    ALife.Creatur.Genetics.Reproduction.Sexual,+                    ALife.Creatur.Logger,+                    ALife.Creatur.Universe,+                    ALife.Creatur.Universe.Task,+                    ALife.Creatur.Util+  Build-Depends:    +                    array ==0.4.*,+                    base ==4.*,+                    base-unicode-symbols ==0.2.*,+                    bytestring ==0.9.*,+                    cereal ==0.3.*,+                    directory ==1.2.*,+                    gray-extended ==1.*,+                    hdaemonize ==0.4.*,+                    hmatrix ==0.14.*,+                    lens ==3.7.*,+                    MonadRandom ==0.1.*,+                    mtl ==2.1.*,+                    old-locale ==1.0.*,+                    process ==1.1.*,+                    random ==1.0.*,+                    split ==0.2.1.*,+                    time ==1.4.*,+                    transformers ==0.3.*,+                    unix ==2.6.*,+                    zlib ==0.5.*++Test-suite creatur-tests+  Type:             exitcode-stdio-1.0+  Main-is:          Main.hs+  GHC-Options:      -Wall+  Hs-source-dirs:   test+  Build-Depends:    +                    array ==0.4.*,+                    base ==4.*,+                    base-unicode-symbols ==0.2.*,+                    binary == 0.5.*,+                    cereal ==0.3.*,+                    creatur,+                    ghc-prim ==0.2.*,+                    hmatrix ==0.14.*,+                    HUnit ==1.2.*,+                    MonadRandom ==0.1.*,+                    mtl ==2.1.*,+                    temporary ==1.1.*,+                    test-framework ==0.6.*,+                    test-framework-hunit ==0.3.*,+                    test-framework-quickcheck2 ==0.2.*,+                    QuickCheck >= 2.5+  Other-modules:    ALife.Creatur.UtilQC+                    ALife.Creatur.Database.FileSystemQC+                    ALife.Creatur.Genetics.CodeQC+                    ALife.Creatur.Genetics.CrossoverQC+
+ src/ALife/Creatur.hs view
@@ -0,0 +1,37 @@+------------------------------------------------------------------------+-- |+-- Module      :  ALife.Creatur+-- Copyright   :  (c) Amy de Buitléir 2012-2013+-- License     :  BSD-style+-- Maintainer  :  amy@nualeargais.ie+-- Stability   :  experimental+-- Portability :  portable+--+-- Definitions use throughout the Créatúr framework.+--+------------------------------------------------------------------------+{-# LANGUAGE UnicodeSyntax, TypeFamilies, FlexibleContexts #-}++module ALife.Creatur+ (+    Agent(..),+    AgentId,+    Time+ ) where++-- | The internal clock used by Créatúr is a simple counter.+type Time = Int++-- | A unique ID associated with an agent.+type AgentId = String++-- | An artificial life species.+--   All species used in Créatúr must be an instance of this class.+class Agent a where++  -- | Returns the agent ID.+  agentId ∷ a → AgentId++  -- | Returns True if the agent is alive, false otherwise.+  isAlive ∷ a → Bool+
+ src/ALife/Creatur/AgentNamer.hs view
@@ -0,0 +1,53 @@+------------------------------------------------------------------------+-- |+-- Module      :  ALife.Creatur.Tools.AgentNamer+-- Copyright   :  (c) Amy de Buitléir 2012-2013+-- License     :  BSD-style+-- Maintainer  :  amy@nualeargais.ie+-- Stability   :  experimental+-- Portability :  portable+--+-- Assigns a unique ID upon request. IDs generated by an @AgentNamer@ +-- are guaranteed to be unique within a given universe, across all +-- simulation runs.+--+------------------------------------------------------------------------+{-# LANGUAGE UnicodeSyntax #-}+module ALife.Creatur.AgentNamer+  (+    AgentNamer(..),+    SimpleAgentNamer,+    mkSimpleAgentNamer+  ) where++import ALife.Creatur (AgentId)+import ALife.Creatur.Counter (PersistentCounter, current, increment,+  mkPersistentCounter)+import ALife.Creatur.Util (stateMap)+import Control.Monad.State (StateT, get, gets)++class AgentNamer n where+  -- | Assign a unique ID using the supplied prefix.+  genName ∷ StateT n IO AgentId++data SimpleAgentNamer = SimpleAgentNamer +  {+    prefix ∷ String,+    counter ∷ PersistentCounter+  }++mkSimpleAgentNamer ∷ String → FilePath → SimpleAgentNamer+mkSimpleAgentNamer s f = SimpleAgentNamer s $ mkPersistentCounter f++withCounter ∷ StateT PersistentCounter IO x → StateT SimpleAgentNamer IO x+withCounter runProgram = do+  u ← get+  stateMap (\c → u {counter=c}) counter runProgram++instance AgentNamer SimpleAgentNamer where+  genName = do+    p ← gets prefix+    k ← withCounter (increment >> current)+    return $ p ++ show k++
+ src/ALife/Creatur/Clock.hs view
@@ -0,0 +1,41 @@+------------------------------------------------------------------------+-- |+-- Module      :  ALife.Creatur.Tools.Logger+-- Copyright   :  (c) Amy de Buitléir 2012-2013+-- License     :  BSD-style+-- Maintainer  :  amy@nualeargais.ie+-- Stability   :  experimental+-- Portability :  portable+--+-- An internal simulation clock which persists between runs. This is a+-- simple counter, completely independent from any system clock or+-- hardware clock. The clock does not automatically advance, it only+-- advances when @'incTime'@ is called. In this way, the Créatúr +-- framework will run consistently, treating all agents fairly,+-- regardless of current processor load. It also ensures that data+-- obtained from simulation runs on different machines with different+-- CPU performance can still be meaningfully compared.+--+------------------------------------------------------------------------+{-# LANGUAGE UnicodeSyntax #-}+module ALife.Creatur.Clock+  (+    Clock(..)+  ) where++import ALife.Creatur (Time)+import Control.Monad.State (StateT)++-- | A clock representing the time in a Créatúr universe.+--   It is used to schedule events and ensure that each agent gets its+--   fair share of the CPU.+--   This clock is entirely separate from the system clock.+--   It advances only when @'incTime'@ is called.+--   This allows Créatúr to run without being affected by other+--   processes which might be using the CPU at the same time.+class Clock c where+  -- | The current time, measured in "ticks"+  currentTime ∷ StateT c IO Time+  -- | Advance the clock to the next "tick".+  incTime ∷ StateT c IO ()+
+ src/ALife/Creatur/Counter.hs view
@@ -0,0 +1,73 @@+------------------------------------------------------------------------+-- |+-- Module      :  ALife.Creatur.Tools.Logger+-- Copyright   :  (c) Amy de Buitléir 2012-2013+-- License     :  BSD-style+-- Maintainer  :  amy@nualeargais.ie+-- Stability   :  experimental+-- Portability :  portable+--+-- A simple counter which persists between runs.+--+------------------------------------------------------------------------+{-# LANGUAGE UnicodeSyntax #-}+module ALife.Creatur.Counter+  (+    Counter(..),+    PersistentCounter,+    mkPersistentCounter+  ) where++import ALife.Creatur.Clock (Clock, currentTime, incTime)+import Control.Monad (unless)+import Control.Monad.IO.Class (liftIO)+import Control.Monad.State (StateT, get, gets, modify, put)+import System.Directory (doesFileExist)++class Counter c where+  current ∷ StateT c IO Int+  increment ∷ StateT c IO ()++data PersistentCounter = PersistentCounter {+    initialised ∷ Bool,+    time ∷ Int,+    filename ∷ FilePath+  } deriving Show++-- | Creates a counter that will store its value in the specified file.+mkPersistentCounter ∷ FilePath → PersistentCounter+mkPersistentCounter = PersistentCounter False (-1)++instance Counter PersistentCounter where+  current = do+    initIfNeeded+    gets time+  increment = do+    t ← current+    let t' = t + 1+    f ← gets filename+    modify (\c → c { time=t' })+    liftIO $ writeFile f $ show t'++initIfNeeded ∷ StateT PersistentCounter IO ()+initIfNeeded = do+  isInitialised ← gets initialised+  unless isInitialised $ do+    counter ← get+    counter' ← liftIO $ initialise counter+    put counter'++initialise ∷ PersistentCounter → IO PersistentCounter+initialise counter = do+  let f = filename counter+  fExists ← doesFileExist f+  if fExists+    then do+      s ← readFile f+      return $ counter { initialised=True, time=read s }+    else return $ counter { initialised=True, time=0 }++instance Clock PersistentCounter where+  currentTime = current+  incTime = increment+
+ src/ALife/Creatur/Daemon.hs view
@@ -0,0 +1,89 @@+------------------------------------------------------------------------+-- |+-- Module      :  ALife.Creatur.Daemon+-- Copyright   :  (c) Amy de Buitléir 2012-2013+-- License     :  BSD-style+-- Maintainer  :  amy@nualeargais.ie+-- Stability   :  experimental+-- Portability :  portable+--+-- Provides a UNIX daemon to run an experiment using the Créatúr+-- framework.+--+------------------------------------------------------------------------+{-# LANGUAGE UnicodeSyntax, TypeFamilies, FlexibleContexts #-}++module ALife.Creatur.Daemon+  (+    Daemon(..),+    launch+  ) where++import Control.Concurrent (MVar, newMVar, readMVar, swapMVar, +  threadDelay)+import Control.Exception (SomeException, handle)+import Control.Monad.State (StateT, execStateT)+import Data.Eq.Unicode ((≠))+import System.IO.Unsafe (unsafePerformIO)+import System.Posix.Daemonize (CreateDaemon(..), serviced, simpleDaemon)+import System.Posix.Signals (Handler(Catch), fullSignalSet, +  installHandler, sigTERM)+import System.Posix.User (getLoginName, getRealUserID)++termReceived ∷ MVar Bool+termReceived = unsafePerformIO (newMVar False)++-- | Daemon configuration.+--   If @username@ ≡ "", the daemon will run under the login name.+data Daemon s = Daemon+  {+    onStartup ∷ s → IO s,+    onShutdown ∷ s → IO (),+    onException ∷ s → SomeException → IO s,+    task ∷ StateT s IO (),+    username ∷ String,+    sleepTime ∷ Int+  }++-- | @'launch' username sleepTime state task@ creates a daemon+--   running as @username@, which invokes @task@ repeatedly, sleeping +--   for @sleepTime@ microseconds between invocations of @task@.+launch ∷ Daemon s → s → IO ()+launch d s = do+  uid ← getRealUserID+  if uid ≠ 0+    then putStrLn "Must run as root"+    else do+      u ← daemonUsername d+      serviced $ simpleDaemon +        { program = daemonMain d s,+          user    = Just u }++daemonUsername ∷ Daemon s → IO String+daemonUsername d =+  if (null . username) d+    then getLoginName+    else (return . username) d+    +daemonMain ∷ Daemon s → s → () → IO ()+daemonMain d s _ = do+  s' ← onStartup d s+  _ ← installHandler sigTERM (Catch handleTERM) (Just fullSignalSet)+  _ ← daemonMainLoop d s'+  return ()++daemonMainLoop ∷ Daemon s → s → IO ()+daemonMainLoop d s = do+  threadDelay . sleepTime $ d+  timeToStop ← readMVar termReceived+  if timeToStop +    then onShutdown d s+    else do+      s' ← handle ((onException d) s) $ execStateT (task d) s+      daemonMainLoop d s'++handleTERM ∷ IO ()+handleTERM = do+  _ ← swapMVar termReceived True+  return ()+
+ src/ALife/Creatur/Database.hs view
@@ -0,0 +1,42 @@+------------------------------------------------------------------------+-- |+-- Module      :  ALife.Creatur.Database+-- Copyright   :  (c) Amy de Buitléir 2012-2013+-- License     :  BSD-style+-- Maintainer  :  amy@nualeargais.ie+-- Stability   :  experimental+-- Portability :  portable+--+-- Database interface for the Créatúr framework.+--+------------------------------------------------------------------------+{-# LANGUAGE UnicodeSyntax, TypeFamilies, FlexibleContexts #-}++module ALife.Creatur.Database+ (+    Database(..),+    Record(..)+ ) where++import Control.Monad.State (StateT)+import Data.Serialize (Serialize)++class Record r where+  key ∷ r → String+  +-- | A database offering storage and retrieval for records.+class Database d where+  type DBRecord d+  -- | Get a list of all keys in the database.+  keys ∷ StateT d IO [String]+  -- | Read a record from the database.+  lookup ∷ Serialize (DBRecord d) ⇒ +    String → StateT d IO (Either String (DBRecord d))+  -- | Write a record to the database. +  --   If an agent with the same name already exists, it will be overwritten.+  store ∷ (Record (DBRecord d), Serialize (DBRecord d)) ⇒ +    DBRecord d → StateT d IO ()+  -- | Remove a record from the database.+  --   The database may archive records rather than simply deleting them.+  delete ∷ Serialize (DBRecord d) ⇒ String → StateT d IO ()+
+ src/ALife/Creatur/Database/FileSystem.hs view
@@ -0,0 +1,98 @@+{-# LANGUAGE UnicodeSyntax, TypeFamilies, FlexibleContexts #-}++-- | Represents r FSDatabase (including agents, clock, logging facility,+--   etc.) that can run within the Créatúr framework.+module ALife.Creatur.Database.FileSystem+  (+    FSDatabase,+    mkFSDatabase+  ) where++import ALife.Creatur.Database (Database(..), DBRecord, Record, +  delete, key, keys, store)+import Prelude hiding (readFile, writeFile)+import Control.Monad (unless, when)+import Control.Monad.IO.Class (liftIO)+import Control.Monad.State (StateT, get, gets, put)+import Data.ByteString as BS (readFile, writeFile)+import qualified Data.Serialize as DS +  (Serialize, decode, encode)+import System.Directory (createDirectoryIfMissing, doesFileExist, +  getDirectoryContents, removeFile)++-- | A simple database where each record is stored in a separate file, and+--   the name of the file is the record's key.+data FSDatabase r = FSDatabase+  {+    initialised ∷ Bool,+    mainDir ∷ FilePath,+    archiveDir ∷ FilePath+  } deriving Show++instance Database (FSDatabase r) where+  type DBRecord (FSDatabase r) = r++  keys = do+    initIfNeeded+    d ← gets mainDir+    files ← liftIO $ getDirectoryContents d+    return $ filter isRecordFileName files++  lookup k = do+    initIfNeeded+    d ← gets mainDir+    let f = d ++ '/':k+    liftIO $ readRecord3 f++  store r = do+    initIfNeeded+    writeRecord2 mainDir r++  delete name = do+    initIfNeeded+    fileExists ← liftIO $ doesFileExist name+    when fileExists $ liftIO $ removeFile name++-- | @'mkFSDatabase' d@ (re)creates the FSDatabase in the+--   directory @d@.+mkFSDatabase ∷ FilePath → FSDatabase r+mkFSDatabase d = FSDatabase False d (d ++ "/archive")++initIfNeeded ∷ StateT (FSDatabase r) IO ()+initIfNeeded = do+  isInitialised ← gets initialised+  unless isInitialised $ do+    u ← get+    u' ← liftIO $ initialise u+    put u'++initialise ∷ FSDatabase r → IO (FSDatabase r)+initialise u = do+  createDirectoryIfMissing True (mainDir u)+  createDirectoryIfMissing True (archiveDir u)+  return u { initialised=True }++-- | Read a record from a file.+readRecord3 ∷ DS.Serialize r ⇒ FilePath → IO (Either String r)+readRecord3 f = do+  x ← readFile f+  return $ DS.decode x++-- | Write a record to a file.+writeRecord3 ∷ (Record r, DS.Serialize r) ⇒ FilePath → r → IO ()+writeRecord3 f a = do+  let x = DS.encode a+  writeFile f x++writeRecord2 ∷ (Record r, DS.Serialize r) ⇒ +  (FSDatabase r → FilePath) → r → StateT (FSDatabase r) IO ()+writeRecord2 dirGetter r = do+  d ← gets dirGetter+  let f = d ++ '/':key r+  liftIO $ writeRecord3 f r+  -- liftIO $ agentId r ++ " archived to " ++ show f     ++isRecordFileName ∷ String → Bool+isRecordFileName s =+  s `notElem` [ "archive", ".", ".." ]+
+ src/ALife/Creatur/Genetics/Code.hs view
@@ -0,0 +1,31 @@+------------------------------------------------------------------------+-- |+-- Module      :  ALife.Creatur.Genetics.Code+-- Copyright   :  (c) Amy de Buitléir 2011-2013+-- License     :  BSD-style+-- Maintainer  :  amy@nualeargais.ie+-- Stability   :  experimental+-- Portability :  portable+--+-- Encoding schemes for genes.+--+------------------------------------------------------------------------+{-# LANGUAGE UnicodeSyntax #-}++module ALife.Creatur.Genetics.Code+  (+    -- * Coding schemes+    Code,+    mkGrayCode,+    -- * Encoding and decoding+    encode,+    encodeNext,+    decode,+    decodeNext,+    -- * Miscellaneous+    asBits+  ) where++import ALife.Creatur.Genetics.CodeInternal (Code, mkGrayCode, encode, +  encodeNext, decode, decodeNext, asBits)+
+ src/ALife/Creatur/Genetics/CodeInternal.hs view
@@ -0,0 +1,92 @@+------------------------------------------------------------------------+-- |+-- Module      :  ALife.Creatur.Genetics.Code+-- Copyright   :  (c) Amy de Buitléir 2011-2013+-- License     :  BSD-style+-- Maintainer  :  amy@nualeargais.ie+-- Stability   :  experimental+-- Portability :  portable+--+-- Encoding schemes for genes.+--+------------------------------------------------------------------------+{-# LANGUAGE UnicodeSyntax #-}++module ALife.Creatur.Genetics.CodeInternal where++import ALife.Creatur.Util (ilogBase, isPowerOf, reverseLookup)+import Codec.Gray (grayCodes)+import Prelude hiding (cycle)++-- | An encoding scheme.+data Code a b = Code { cSize ∷ Int, cTable ∷ [(a,[b])] } deriving Show++-- | Encodes a value as a sequence of bits.+encode ∷ Eq a ⇒ Code a b → a → Maybe [b]+encode = flip lookup . cTable++---- | Given a list of encoding schemes paired with genes, encode all of the+----   genes. Unencodable genes will be skipped.+--encodeAll ∷ Eq a ⇒ [(Code a, a)] → [b]+--encodeAll ps = foldr encodeNext [] ps++encodeNext ∷ Eq a ⇒ (Code a b, a) → [b] → [b]+encodeNext (c, a) bs = maybe bs (bs ++) (encode c a)++-- | Returns the value corresponding to a sequence of bits.+decode ∷ Eq b ⇒ Code a b → [b] → Maybe a+decode = flip reverseLookup . cTable++--decodeAll _ [] = []+--decodeAll bs (c:cs) = g:gs'+--  where g = decode c bs1+--        (bs1, bs2) = splitAt (cSize c) bs+--        gs' = decodeAll bs2 cs++decodeNext ∷ Eq b ⇒ Code a b → [b] → (Maybe a, [b])+decodeNext c bs = (decode c bs1, bs2)+  where (bs1, bs2) = splitAt (cSize c) bs++-- | Convert a list of bits to a string of @0@s and @1@s.+asBits ∷ [Bool] → String+asBits = map (\b → if b then '1' else '0')++-- | Constructs a Gray code for the specified values, using the minimum number+--   of bits required to encode all of the values.+--+--   If the number of values provided is not a perfect square, some codes will+--   not be used; calling @decode@ with those values will return @Nothing@.+--   You can find out if this will be the case by calling @'excessGrayCodes'@.+--   For example @mkGrayCode [\'a\',\'b\',\'c\']@ would assign the code+--   @00@ to @'a'@, @01@ to @'b'@, and @11@ to @'c'@, leaving @10@ unassigned.+--   To avoid having unassigned codes, you can repeat a value in the input +--   list so the example above could be modified to  +--   @mkGrayCode [\'a\',\'a\',\'b\',\'c\']@, which would assign the codes+--   @00@ and @01@ to 'a', @11@ to @'b'@, and @10@ to @'c'@.+--+--   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 will 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.+mkGrayCode ∷ [a] → Code a Bool+mkGrayCode xs = Code k (zip xs cs)+  where n = grayCodeLength $ length xs+        k = (length . head) cs+        cs = grayCodes n++-- | @'grayCodeLength' n@ returns the number of bits required to encode @n@+--   values.+grayCodeLength ∷ Int → Int+grayCodeLength n = if n `isPowerOf` 2 then k else k + 1+  where k = ilogBase (2 ∷ Int) n++-- | @'grayCodeCapacity' n@ returns the number of values that can be encoded+--   using @n@ bits. The number of values that can be encoded in n bits is +--   2^n.+grayCodeCapacity ∷ Int → Int+grayCodeCapacity n = 2^n++
+ src/ALife/Creatur/Genetics/Gene.hs view
@@ -0,0 +1,47 @@+------------------------------------------------------------------------+-- |+-- Module      :  ALife.Creatur.Genetics.Gene+-- Copyright   :  (c) Amy de Buitléir 2011-2013+-- License     :  BSD-style+-- Maintainer  :  amy@nualeargais.ie+-- Stability   :  experimental+-- Portability :  portable+--+-- Definitions related to artificial genes.+--+------------------------------------------------------------------------+{-# LANGUAGE UnicodeSyntax #-}++module ALife.Creatur.Genetics.Gene +  (+--    ACTG(..),+    PairedGene(..),+    decodeAndExpress+  ) where++import ALife.Creatur.Genetics.Code (Code, decodeNext)++-- | A paired instruction for building an agent.+class PairedGene g where+  -- | Given two possible forms of a PairedGene, @'express'@ takes into+  --   account any dominance relationship, and returns a PairedGene+  --   representing the result.+  express ∷ g → g → g++-- | Read the next pair of PairedGenes from a two sequences of +--   "nucleotides", and return the resulting PairedGene (after taking+--   into account any dominance relationship) and the remaining (unread)+--   portion of the two nucleotide strands.+decodeAndExpress ∷ +  (PairedGene g, Eq n) ⇒ Code g n → ([n], [n]) → (Maybe g, ([n], [n]))+decodeAndExpress c (as,bs) = (expressMaybe a b, (as',bs'))+  where (a,as') = decodeNext c as+        (b,bs') = decodeNext c bs++expressMaybe ∷ PairedGene g ⇒ Maybe g → Maybe g → Maybe g+expressMaybe (Just a) (Just b) = Just (express a b)+expressMaybe (Just a) Nothing  = Just a+expressMaybe Nothing (Just b)  = Just b+expressMaybe Nothing Nothing   = Nothing++
+ src/ALife/Creatur/Genetics/Recombination.hs view
@@ -0,0 +1,174 @@+------------------------------------------------------------------------+-- |+-- Module      :  ALife.Creatur.Genetics.Recombination+-- Copyright   :  (c) Amy de Buitléir 2011-2013+-- License     :  BSD-style+-- Maintainer  :  amy@nualeargais.ie+-- Stability   :  experimental+-- Portability :  portable+--+-- Provides a mechanism to break apart and rejoin sequences of data. +-- Inspired by DNA recombination in biology, this technique can be used+-- to recombine \"genetic\" instructions for building artificial life.+--+------------------------------------------------------------------------+{-# LANGUAGE UnicodeSyntax #-}++module ALife.Creatur.Genetics.Recombination+  (+    crossover,+    cutAndSplice,+    mutateList,+    mutatePairedLists,+    randomOneOfList,+    randomOneOfPair,+    randomCrossover,+    randomCutAndSplice,+    repeatWithProbability,+    withProbability+  ) where++import ALife.Creatur.Util (safeReplaceElement)++import System.Random (Random)+import Control.Monad.Random (Rand, RandomGen, getRandom, getRandomR)++-- | Cuts two lists at the specified locations, swaps the ends, and +--   splices them. The resulting lists will be:+--   @+--     a[0..n-1] ++ b[m..]+--     b[0..m-1] ++ a[n..]+--   @+--   Here are some examples.+--   @+--     /Expression/                               /Result/+--     'cutAndSplice' 2 5 (\"abcdef\", \"ABCDEF\")    (\"abF\",\"ABCDEcdef\")+--     'cutAndSplice' 3 1 (\"abcd\", \"ABCDEFG\")     (\"abcBCDEFG\",\"Ad\")+--     'cutAndSplice' 4 4 (\"abcdef\", \"ABCDEF\")    (\"abcdEF\",\"ABCDef\")+--   @+--   If n ≤ 0 or m ≤ 0, the corresponding input list will be completely+--   transferred to the other.+--   @+--     /Expression/                               /Result/+--     'cutAndSplice' 0 4 (\"abcdef\", \"ABCDEF\")    (\"EF\",\"ABCDabcdef\")+--     'cutAndSplice' (-2) 4 (\"abcd\", \"ABCDEFGH\") (\"EFGH\",\"ABCDabcd\")+--     'cutAndSplice' 5 0 (\"abcdef\", \"ABCDEF\")    (\"abcdeABCDEF\",\"f\")+--   @+--   If n or m are greater than or equal to length of the corresponding list,+--   that list will not be transferred.+--   @+--     /Expression/                               /Result/+--     'cutAndSplice' 10 0 (\"abcdef\", \"ABCDEF\")   (\"abcdefABCDEF\",\"\")+--     'cutAndSplice' 0 0 (\"\", \"ABCDEF\")          (\"ABCDEF\",\"\")+--   @+cutAndSplice ∷ Int → Int → ([a], [a]) → ([a], [a])+cutAndSplice n m (as, bs) = (cs, ds)+    where cs = as1 ++ bs2+          ds = bs1 ++ as2+          (as1, as2) = splitAt n as+          (bs1, bs2) = splitAt m bs++-- | Same as @'cutAndSplice'@, except that the two locations are+--   chosen at random.+randomCutAndSplice ∷ (RandomGen g) ⇒ ([a], [a]) → Rand g ([a], [a])+randomCutAndSplice (as, bs) = do+    n ← getRandomR (0,length as - 1)+    m ← getRandomR (0,length bs - 1)+    return (cutAndSplice n m (as, bs))++-- | Cuts two lists at the specified location, swaps the ends, and +--   splices them. This is a variation of 'cutAndSplice' where n ≡ m.+crossover ∷ Int → ([a], [a]) → ([a], [a])+crossover n = cutAndSplice n n++-- | Same as @'crossover'@, except that the location is chosen at +--   random.+randomCrossover ∷ (RandomGen g) ⇒ ([a], [a]) → Rand g ([a], [a])+randomCrossover (as, bs) = do+    n ← getRandomR (0,length as - 1)+    return (crossover n (as, bs))++-- | Mutates a random element in the list.+mutateList ∷ (Random n, RandomGen g) ⇒ [n] → Rand g [n]+mutateList xs = do+  (i, _) ← randomListSelection xs+  x ← getRandom+  return (safeReplaceElement xs i x)++-- | Mutates a random element in one list in a pair.+mutatePairedLists ∷ +  (Random n, RandomGen g) ⇒ ([n], [n]) → Rand g ([n], [n])+mutatePairedLists (xs,ys) = do+  chooseFst ← weightedRandomBoolean 0.5+  if chooseFst +    then do+      xs' ← mutateList xs+      return (xs', ys)+    else do+      ys' ← mutateList ys+      return (xs, ys')++-- | Performs an operation with the specified probability.+withProbability ∷ RandomGen g ⇒ Double → (b → Rand g b) → b → Rand g b+withProbability p op genes = do+  doOp ← weightedRandomBoolean p+  if doOp then op genes else return genes++-- | Performs an operation a random number of times.+--   The probability of repeating the operation @n@ times is @p^n@.+repeatWithProbability ∷ RandomGen g ⇒ Double → (b → Rand g b) → b → Rand g b+repeatWithProbability p op genes = do+  doOp ← weightedRandomBoolean p+  if doOp +    then do+      genes' ← op genes+      repeatWithProbability p op genes'+    else return genes+++-- :m + ALife.Creatur.Genetics.Gene+-- let g = (replicate 10 A, replicate 10 C)+-- evalRandIO (withProbability 0.1 randomCrossover g >≥ withProbability 0.01 randomCutAndSplice >≥ withProbability 0.001 mutatePairedLists)+-- Any mixing of As and Cs will be the result of crossover (if the lengths are the same) or cut-and-splice (if the lengths are different).+-- Any Gs or Ts that show up are the result of mutation.+-- evalRandIO (withProbability 0.5 randomCrossover g >≥ withProbability 0.05 randomCutAndSplice >≥ withProbability 0.5 mutatePairedLists >≥ randomOneOfPair)+++-- | Randomly select a boolean, but weighted to return True with probability +--   p.+weightedRandomBoolean ∷ (RandomGen g) ⇒ Double → Rand g Bool+weightedRandomBoolean p = do+  x ← getRandomR (0.0,1.0)+  return (x < p)++randomOneOfPair ∷ (RandomGen g) ⇒ (a, a) → Rand g a+randomOneOfPair pair = do+  chooseFst ← weightedRandomBoolean 0.5+  if chooseFst +    then return . fst $ pair+    else return . snd $ pair++randomOneOfList ∷ (RandomGen g) ⇒ [a] → Rand g a+randomOneOfList xs = do+  (_, z) ← randomListSelection xs+  return z++---- | Sample a random element from a weighted list.+----   The total weight of all elements must not be 0.+---- Adapted from the code in MonadRandom+--randomWeightedChoice ∷ (RandomGen g) ⇒ [(a, Double)] → Rand g a+--randomWeightedChoice [] = error "randomFromList called with empty list"+--randomWeightedChoice [(x,_)] = return x+--randomWeightedChoice xs = do+--  let s = sum $ map snd xs -- total weight+--  let cs = scanl1 (\(_,q) (y,s') → (y, s'+q)) xs     -- cumulative weight+--  p ← getRandomR (0.0,s)+--  return (fst (head (dropWhile (\(_,q) → q < p) cs)))++-- | Choose an element at random from a list and return the element and its +--   index+randomListSelection ∷ (RandomGen g) ⇒ [a] → Rand g (Int, a)+randomListSelection xs = do+  i ← getRandomR (0,length xs - 1)+  return (i, xs !! i)+
+ src/ALife/Creatur/Genetics/Reproduction/Asexual.hs view
@@ -0,0 +1,58 @@+------------------------------------------------------------------------+-- |+-- 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 UnicodeSyntax, 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++  -- | The basic unit of hereditary information for an agent.+  --   The type signature for the agent's genome is [Base a].+  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.+  --   2. 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] → Maybe 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 (Maybe a)+  makeOffspring a b name = do+    g ← recombine a b+    return $ build name g+++
+ src/ALife/Creatur/Genetics/Reproduction/Sexual.hs view
@@ -0,0 +1,58 @@+------------------------------------------------------------------------+-- |+-- Module      :  ALife.Creatur.Genetics.Reproduction.Sexual+-- 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 /two/ strands of genetic information.+-- * Each child has two parents.+-- * Each parent contributes approximately half of its genetic+--   information to the offspring.+--+------------------------------------------------------------------------+{-# LANGUAGE UnicodeSyntax, TypeFamilies #-}+module ALife.Creatur.Genetics.Reproduction.Sexual+  (+    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++  -- | The basic unit of hereditary information for an agent.+  --   The type signature for the agent's genome is +  --   ([Base a], [Base a]).+  type Base 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]++  -- | Builds an agent based on the genome provided, if it is possible+  --   to do so.+  build ∷ AgentId → ([Base a], [Base a]) → Maybe 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 @'produceGamete'@ to produce a single strand of genetic+  --      information from each parent.+  --   2. Pairs the two strands to create a genome for the child.+  --   3. Calls @'build' construct a child with this genome.+  makeOffspring ∷ RandomGen r ⇒ a → a → AgentId → Rand r (Maybe a)+  makeOffspring a b name = do+    ga ← produceGamete a+    gb ← produceGamete b+    return $ build name (ga, gb)+
+ src/ALife/Creatur/Logger.hs view
@@ -0,0 +1,102 @@+------------------------------------------------------------------------+-- |+-- Module      :  ALife.Creatur.Tools.Logger+-- Copyright   :  (c) Amy de Buitléir 2011-2013+-- License     :  BSD-style+-- Maintainer  :  amy@nualeargais.ie+-- Stability   :  experimental+-- Portability :  portable+--+-- A simple rotating log, tailored to the needs of the Créatúr +-- framework.+--+------------------------------------------------------------------------+{-# LANGUAGE UnicodeSyntax    #-}+{-# OPTIONS_GHC -fno-warn-type-defaults #-}++module ALife.Creatur.Logger+  (+    Logger(..),+    SimpleRotatingLogger,+    mkSimpleRotatingLogger+  ) where++import Control.Monad (when, unless)+import Control.Monad.IO.Class (liftIO)+import Control.Monad.State (StateT, get, gets, put)+import Data.Eq.Unicode ((≡))+import Data.Time (formatTime, getZonedTime)+import System.Directory (createDirectoryIfMissing, doesFileExist, renameFile)+import System.Locale (defaultTimeLocale)++class Logger l where+  -- | @'write' msg@ formats and writes a new log message.+  writeToLog ∷ String → StateT l IO ()++-- | A rotating logger.+data SimpleRotatingLogger = SimpleRotatingLogger {+    initialised ∷ Bool,+    directory ∷ FilePath,+    logFilename ∷ FilePath,+    expFilename ∷ FilePath,+    maxRecordsPerFile ∷ Int,+    recordCount ∷ Int+  } deriving Show++-- | @'mkSimpleRotatingLogger' d prefix n@ creates a logger that will write to+--   directory @d@. The log \"rotates\" (starts a new log file) every @n@+--   records. Log files follow the naming convention @prefix@./k/, where /k/ +--   is the number of the last log record contained in the file. If logging+--   has already been set up in @directory@, then logging will continue where+--   it left off; appending to the most recent log file.+mkSimpleRotatingLogger ∷ FilePath → String → Int → SimpleRotatingLogger+mkSimpleRotatingLogger d pre n = SimpleRotatingLogger False d fLog fExp n (-1)+  where fLog = d ++ "/" ++ pre ++ ".log"+        fExp = d ++ "/" ++ pre ++ ".exp"++instance Logger SimpleRotatingLogger where+  writeToLog msg = do+    initIfNeeded+    logger ← get+    logger' ← liftIO $ bumpRecordCount logger+    put logger'+    liftIO $ write' logger' msg++initIfNeeded ∷ StateT SimpleRotatingLogger IO ()+initIfNeeded = do+  isInitialised ← gets initialised+  unless isInitialised $ do+    logger ← get+    logger' ← liftIO $ initialise logger+    put logger'++initialise ∷ SimpleRotatingLogger → IO SimpleRotatingLogger+initialise logger = do+  createDirectoryIfMissing True (directory logger)+  let fExp = expFilename logger+  expFileExists ← doesFileExist fExp+  if expFileExists+    then do+      s ← readFile fExp+      return $ logger { initialised=True, recordCount=read s}+    else return $ logger { initialised=True, recordCount=0}++write' ∷ SimpleRotatingLogger → String → IO ()+write' logger msg = do+  timestamp ←+      fmap (formatTime defaultTimeLocale "%y%m%d%H%M%S%z") getZonedTime+  appendFile (logFilename logger)+    $ timestamp ++ "\t" ++ msg ++ "\n"++bumpRecordCount ∷ SimpleRotatingLogger → IO SimpleRotatingLogger+bumpRecordCount logger = do+  let n = 1 + recordCount logger+  when (0 ≡ n `mod` maxRecordsPerFile logger) $ liftIO $ rotateLog logger+  writeFile (expFilename logger) (show n)+  return logger{ recordCount=n }++rotateLog ∷ SimpleRotatingLogger → IO ()+rotateLog logger = do+  let f = logFilename logger+  renameFile f $ f ++ '.' : (show . recordCount) logger+
+ src/ALife/Creatur/Universe.hs view
@@ -0,0 +1,105 @@+------------------------------------------------------------------------+-- |+-- Module      :  ALife.Creatur.Universe+-- Copyright   :  (c) Amy de Buitléir 2012-2013+-- License     :  BSD-style+-- Maintainer  :  amy@nualeargais.ie+-- Stability   :  experimental+-- Portability :  portable+--+-- TODO: fill in+--+------------------------------------------------------------------------+{-# LANGUAGE UnicodeSyntax, TemplateHaskell, TypeFamilies, +    FlexibleContexts #-}++module ALife.Creatur.Universe+ (+    Universe(..),+    SimpleUniverse,+    mkSimpleUniverse,+    agentDB,+    clock,+    logger,+    multiLookup,+    extra,+    agentIds,+    addAgent,+    storeOrArchive+ ) where++import ALife.Creatur (Agent, AgentId, agentId, isAlive)+import ALife.Creatur.AgentNamer (AgentNamer, SimpleAgentNamer, +  mkSimpleAgentNamer)+import qualified ALife.Creatur.AgentNamer as N (genName)+import ALife.Creatur.Clock (Clock, currentTime, incTime)+import ALife.Creatur.Counter (PersistentCounter, mkPersistentCounter)+import ALife.Creatur.Database as D (Database, DBRecord, Record,+  delete, keys, lookup, store)+import ALife.Creatur.Database.FileSystem (FSDatabase, mkFSDatabase)+import ALife.Creatur.Logger (Logger, SimpleRotatingLogger, +  mkSimpleRotatingLogger, writeToLog)+import Control.Lens (makeLenses, zoom)+import Control.Monad (unless)+import Control.Monad.State (StateT)+import Data.Either (partitionEithers)+import Data.Serialize (Serialize)++-- | A habitat containing artificial life.+data Universe c l d n x a = Universe {+    _clock ∷ c,+    _logger ∷ l,+    _agentDB ∷ d,+    _namer ∷ n,+    _extra ∷ x+  }++makeLenses ''Universe++instance (Clock c, Logger l) ⇒ Logger (Universe c l d n x a) where+  writeToLog msg = do+    t ← currentTime+    zoom logger $ writeToLog $ show t ++ "\t" ++ msg++instance Clock c ⇒ Clock (Universe c l d n x a) where+  currentTime = zoom clock currentTime+  incTime = zoom clock incTime++instance AgentNamer n ⇒ AgentNamer (Universe c l d n x a) where+  genName = zoom namer N.genName++agentIds ∷ Database d ⇒ StateT (Universe c l d n x a) IO [String]+agentIds = zoom agentDB keys++multiLookup ∷ (Serialize a, Database d, Record a, a ~ DBRecord d) ⇒ +  [AgentId] → StateT d IO (Either String [DBRecord d])+multiLookup names = do+  results ← mapM D.lookup names+  let (msgs, agents) = partitionEithers results+  if null msgs+    then return $ Right agents+    else return . Left . show $ msgs++storeOrArchive ∷ +  (Serialize a, Database d, Record a, Agent a, a ~ DBRecord d) ⇒+    a → StateT d IO ()+storeOrArchive a = do+  store a -- Even dead agents should be stored (prior to archiving)+  unless (isAlive a) $ (delete . agentId) a++addAgent ∷ (Serialize a, Database d, Record a, a ~ DBRecord d) ⇒+     DBRecord d → StateT (Universe c l d n x a) IO ()+addAgent a = zoom agentDB (store a)++type SimpleUniverse a = +  Universe PersistentCounter SimpleRotatingLogger (FSDatabase a)+    SimpleAgentNamer () a++mkSimpleUniverse ∷ String → FilePath → Int → SimpleUniverse a+mkSimpleUniverse name dir rotateCount = Universe c l d n ()+  where c = mkPersistentCounter (dir ++ "/clock")+        l = mkSimpleRotatingLogger (dir ++ "/log/") name rotateCount+        d = mkFSDatabase (dir ++ "/db")+        n = mkSimpleAgentNamer (name ++ "_") (dir ++ "/namer")++
+ src/ALife/Creatur/Universe/Task.hs view
@@ -0,0 +1,149 @@+------------------------------------------------------------------------+-- |+-- Module      :  ALife.Creatur.Universe.Task+-- Copyright   :  (c) Amy de Buitléir 2012-2013+-- License     :  BSD-style+-- Maintainer  :  amy@nualeargais.ie+-- Stability   :  experimental+-- Portability :  portable+--+-- TODO: fill in+--+------------------------------------------------------------------------+{-# LANGUAGE UnicodeSyntax, TypeFamilies, FlexibleContexts #-}++module ALife.Creatur.Universe.Task+ (+    AgentProgram,+    AgentsProgram,+    withAgent,+    withAgents,+    runNoninteractingAgents,+    runInteractingAgents,+    simpleDaemon,+    startupHandler,+    shutdownHandler,+    exceptionHandler+ ) where++import ALife.Creatur (Agent, AgentId)+import ALife.Creatur.Clock (Clock, incTime)+import ALife.Creatur.Daemon (Daemon(..))+import ALife.Creatur.Database as D (Database, DBRecord, Record,+  lookup)+import ALife.Creatur.Logger (Logger, writeToLog)+import ALife.Creatur.Util (rotate, shuffle)+import ALife.Creatur.Universe (Universe, agentDB, clock, multiLookup,+  agentIds, storeOrArchive)+import Control.Exception (SomeException)+import Control.Lens (zoom)+import Control.Monad.IO.Class (liftIO)+import Control.Monad.Random (evalRandIO)+import Control.Monad.Unicode ((≫=))+import Control.Monad.State (StateT, execStateT)+import Data.Eq.Unicode ((≡))+import Data.List (unfoldr)+import Data.Serialize (Serialize)++simpleDaemon ∷ Logger u ⇒ Daemon u+simpleDaemon = Daemon+  {+    onStartup = startupHandler,+    onShutdown = shutdownHandler,+    onException = exceptionHandler,+    task = undefined,+    username = "",+    sleepTime = 100000+  }++startupHandler ∷ Logger u ⇒ u → IO u+startupHandler = execStateT (writeToLog "Starting")++shutdownHandler ∷ Logger u ⇒ u → IO ()+shutdownHandler u = do+  _ ← execStateT (writeToLog "Shutdown requested") u+  return ()++exceptionHandler ∷ Logger u ⇒ u → SomeException → IO u+exceptionHandler u x = execStateT (writeToLog ("WARNING: " ++ show x)) u++-- | A program for an agent which doesn't interact with other agents.+--   The input parameter is the agent whose turn it is to use the CPU.+--   The program must return the agent (which may have been modified).+--   (The universe will then be updated with these changes.)+type AgentProgram c l d n x a = a → StateT (Universe c l d n x a) IO a++withAgent ∷ (Clock c, Logger l, Database d, Agent a, Serialize a, +    Record a, a ~ DBRecord d) ⇒+  AgentProgram c l d n x a → AgentId → +    StateT (Universe c l d n x a) IO ()+withAgent program name = +  (zoom agentDB . D.lookup) name ≫= withAgent' program name++withAgent' ∷ (Clock c, Logger l, Database d, Agent a, Serialize a, +    Record a, a ~ DBRecord d) ⇒+  AgentProgram c l d n x a → AgentId → Either String a → +    StateT (Universe c l d n x a) IO ()+withAgent' _ name (Left msg) = +  writeToLog $ "Unable to read '" ++ name ++ "': " ++ msg+withAgent' program _ (Right a) = +  program a ≫= zoom agentDB . storeOrArchive++runNoninteractingAgents ∷ (Clock c, Logger l, Database d, Agent a, Serialize a, +    Record a, a ~ DBRecord d) ⇒+  AgentProgram c l d n x a → StateT (Universe c l d n x a) IO ()+runNoninteractingAgents agentProgram = do+  xs ← agentIds+  xs' ← liftIO $ evalRandIO $ shuffle xs+  -- TODO Write out current list so we can pick up where we left off????+  --      (when (currentTime logger `mod` 1000 ≡ 0) $ logStats universe logger)+  mapM_ (withAgent agentProgram) xs'+  zoom clock incTime++-- | A program which allows an agent to interact with one or more of+--   its neighbours.+--+--   The input parameter is a list of agents. The first agent in the+--   list is the agent whose turn it is to use the CPU. The rest of+--   the list contains agents it could interact with. For example, if+--   agents reproduce sexually, the program might check if the first+--   agent in the list is female, and the second one is male, and if so,+--   mate them to produce offspring. The input list is generated in a+--   way that guarantees that every possible sequence of agents has an+--   equal chance of occurring.+--+--   The program must return a list of agents that it has modified.+--   (The universe will then be updated with these changes.)+--   The order of the output list is not important.+type AgentsProgram c l d n x a = +  [a] → StateT (Universe c l d n x a) IO [a]++withAgents ∷ (Clock c, Logger l, Database d, Agent a, Serialize a, +    Record a, a ~ DBRecord d) ⇒+  AgentsProgram c l d n x a → [AgentId] →+    StateT (Universe c l d n x a) IO ()+withAgents program names = +  (zoom agentDB . multiLookup) names ≫= withAgents' program++withAgents' ∷ (Clock c, Logger l, Database d, Agent a, Serialize a, +  Record a, a ~ DBRecord d) ⇒+    AgentsProgram c l d n x a → Either String [a] →+      StateT (Universe c l d n x a) IO ()+withAgents' _ (Left msg) = +  writeToLog $ "Database error: " ++ msg+withAgents' program (Right as) = +  program as ≫= mapM_ (zoom agentDB . storeOrArchive)++runInteractingAgents ∷ (Clock c, Logger l, Database d, Agent a, +  Serialize a, Record a, a ~ DBRecord d) ⇒+    AgentsProgram c l d n x a → StateT (Universe c l d n x a) IO ()+runInteractingAgents agentsProgram = do+  xs ← agentIds+  xs' ← liftIO $ evalRandIO $ shuffle xs+  mapM_ (withAgents agentsProgram) $ makeViews xs'+  zoom clock incTime++makeViews ∷ [a] → [[a]]+makeViews as = unfoldr f (0,as)+  where f (n,xs) = if n ≡ length xs then Nothing else Just (rotate xs,(n+1,rotate xs))+
+ src/ALife/Creatur/Util.hs view
@@ -0,0 +1,170 @@+------------------------------------------------------------------------+-- |+-- Module      :  ALife.Creatur.Util+-- Copyright   :  (c) Amy de Buitléir 2011-2013+-- License     :  BSD-style+-- Maintainer  :  amy@nualeargais.ie+-- Stability   :  experimental+-- Portability :  portable+--+-- Utility functions that don't fit anywhere else.+--+------------------------------------------------------------------------+{-# LANGUAGE UnicodeSyntax #-}++module ALife.Creatur.Util+  (+--    constrain,+    cropRect,+    cropSquare,+    perfectSquare,+    ilogBase,+    isPowerOf,+    isqrt,+    replaceElement,+    reverseLookup,+    rotate,+    safeReplaceElement,+    shuffle,+    stateMap+  ) where++import Control.Monad (forM_, liftM)+import Control.Monad.Random (Rand, RandomGen, getRandomRs)+import Control.Monad.State (StateT(..))+import Data.Array.ST (runSTArray)+import Data.Eq.Unicode ((≡))+import Data.List.Split (chunksOf)+import Data.Ord.Unicode ((≤), (≥))+import GHC.Arr (elems, listArray, readSTArray, thawSTArray, writeSTArray)++-- constrain ∷ Ord a ⇒ (a, a) → a → a+-- constrain (a,b) x | b < a     = error "Invalid range"+--                   | x < a     = a+--                   | x > b     = b+--                   | otherwise = x++-- | From <http://www.haskell.org/haskellwiki/Random_shuffle>+shuffle ∷ RandomGen g ⇒ [a] → Rand g [a]+shuffle xs = do+  let l = length xs+  rands ← take l `fmap` getRandomRs (0, l-1)+  let ar = runSTArray $ do+      ar' ← thawSTArray $ listArray (0, l-1) xs+      forM_ (zip [0..(l-1)] rands) $ \(i, j) → do+          vi ← readSTArray ar' i+          vj ← readSTArray ar' j+          writeSTArray ar' j vi+          writeSTArray ar' i vj+      return ar'+  return (elems ar)++-- | @'safeReplaceElement' xs n x@ returns a copy of @xs@ in which the @n@th+--   element (if it exists) has been replaced with @x@.+safeReplaceElement ∷ [a] → Int → a → [a]+safeReplaceElement xs i x =+  if i ≥ 0 && i < length xs+    then replaceElement xs i x+    else xs++-- | @'replaceElement' xs n x@ returns a copy of @xs@ in which the @n@th+--   element has been replaced with @x@. Causes an exception if @xs@ has+--   fewer than @n+1@ elements. Compare with @'safeReplaceElement'@.+replaceElement ∷ [a] → Int → a → [a]+replaceElement xs i x = +  if 0 ≤ i && i < length xs then fore ++ (x : aft) else xs+  where fore = take i xs+        aft = drop (i+1) xs++-- | Assuming @xs@ is a sequence containing the elements of a square matrix,+--   @'cropSquare' n xs@ returns the elements of the submatrix of size @n@x@n@,+--   centred within the original matrix @xs@.+--+--   Example: Suppose we have a /5/x/5/ matrix and we want to extract the+--   central /3/x/3/ submatrix, as illustrated below.+--+-- > a b c d e+-- > f g h i j            g h i+-- > k l m n o    --→    l m n+-- > p q r s t            q r s+-- > u v w x y+--+--   We can represent the elements of the original matrix as @[\'a\'..\'y\']@.+--   The elements of the submatrix are+--   @[\'g\', \'h\', \'i\', \'l\', \'m\', \'n\', \'q\', \'r\', \'s\']@,+--   or equivalently, @\"ghilmnqrs\"@. And that is what+--   @'cropSquare' 3 [\'a\'..\'y\']@ returns.+cropSquare ∷ Int → [a] → [a]+cropSquare n xs | n ≤ 0     = []+                | n < m     = +                    cropRect (margin, margin) (margin+n-1, margin+n-1) xs m+                | otherwise = take (m*m) xs+  where m = (isqrt . length) xs+        margin = (m - n) `div` 2++-- | Assuming @xs@ is a sequence containing the elements of a matrix with @k@+--   columns, @'cropRect' (a,b) (c, d) k xs@ returns the elements of the+--   submatrix from @(a,b)@ in the upper left corner to @(c,d)@ in the lower+--   right corner).+--   Note: Matrix indices begin at @(0,0)@.+--+--   Example: Suppose we have a /4/x/6/ matrix and we want to extract the+--   submatrix from (1,2) to (2,4), as illustrated below.+--+-- > a b c d e f+-- > g h i j k l    --→   i j k+-- > m n o p q r           o p q+-- > s t u v w x+--+--   We can represent the elements of the original matrix as @[\'a\'..\'x\']@.+--   The elements of the submatrix are+--   @[\'i\', \'j\', \'k\', \'o\', \'p\', \'q\']@, or equivalently,+--   @\"ijkopq\"@. And that is what @'cropRect' (1,2) (2,4) 6 [\'a\'..\'x\']@+--   returns.+cropRect ∷ (Int, Int) → (Int, Int) → [a] → Int → [a]+cropRect (a,b) (c, d) xs k = concatMap f selectedRows+  where rows = if k ≤ 0 then [] else chunksOf k xs+        selectedRows = safeSlice a c rows+        f = safeSlice b d++safeSlice ∷ Int → Int → [a] → [a]+safeSlice a b = drop a . take (b+1)++-- | @'isqrt' n@ returns the greatest integer not greater than the square root+--   of @n@.+isqrt ∷ (Integral a, Integral b) ⇒ a → b+isqrt n = (floor . sqrt) n'+  where n' = fromIntegral n ∷ Float++-- | @'ilogBase' n m@ returns the greatest integer not greater than the log+--   base n of @m@.+ilogBase ∷ (Integral a, Integral b, Integral c) ⇒ a → b → c+ilogBase n m = (floor . logBase n') m'+  where n' = fromIntegral n ∷ Float+        m' = fromIntegral m ∷ Float++-- | @'perfectSquare' n@ returns @True@ if @n@ is a perfect square (i.e., if +--   there exists an _integer_ m such that m*m = n)+perfectSquare ∷ Integral a ⇒ a → Bool+perfectSquare n = n ≡ m*m+  where m = isqrt n++-- | @n 'isPowerOf' m@ returns @True@ if @n@ is a power of m (i.e., if +--   there exists an _integer_ k such that m^k = n)+isPowerOf ∷ Integral a ⇒ a → a → Bool+isPowerOf n m = n ≡ m^k+  where k = ilogBase m n ∷ Int++reverseLookup ∷ (Eq b) ⇒ b → [(a,b)] → Maybe a+reverseLookup _ []          =  Nothing+reverseLookup value ((x,y):xys)+    | value ≡ y =  Just x+    | otherwise  =  reverseLookup value xys++stateMap ∷ Monad m ⇒ (s → t) → (t → s) → StateT s m a → StateT t m a+stateMap f g (StateT h) = StateT $ liftM (fmap f) . h . g++rotate ∷ [a] → [a]+rotate [] = []+rotate (x:xs) = xs ++ [x]+
+ test/ALife/Creatur/Database/FileSystemQC.hs view
@@ -0,0 +1,45 @@+{-# LANGUAGE UnicodeSyntax, DeriveGeneric #-}++module ALife.Creatur.Database.FileSystemQC+  (+    test+  ) where++import Prelude hiding (lookup)+import ALife.Creatur.Database (Record, key, store, lookup)+import ALife.Creatur.Database.FileSystem (mkFSDatabase)+import Control.Monad.State (execStateT, evalStateT)+import Data.Serialize (Serialize)+import GHC.Generics (Generic)+import System.IO.Temp (withSystemTempDirectory)+import Test.Framework as TF (Test, testGroup)+import Test.HUnit as TH (assertEqual)+import Test.Framework.Providers.HUnit (testCase)++data TestRecord = TestRecord String Int+  deriving (Show, Eq, Generic)++instance Record TestRecord where+  key (TestRecord k _) = k++instance Serialize TestRecord++tryStoreLookup ∷ FilePath → IO ()+tryStoreLookup dir = do+  let db = mkFSDatabase dir+  let record = TestRecord "alpha" 7+  db' ← execStateT (store record) db+  Right record' ← evalStateT (lookup (key record)) db'+  assertEqual "wombat" record record'++testStoreLookup ∷ IO ()+testStoreLookup = withSystemTempDirectory "creaturTest.tmp" tryStoreLookup++test ∷ TF.Test+test = testGroup "HUnit ALife.Creatur.Database.FileSystemQC"+  [+    testCase "store and lookup"+      testStoreLookup+  ]++
+ test/ALife/Creatur/Genetics/CodeQC.hs view
@@ -0,0 +1,85 @@+{-# LANGUAGE UnicodeSyntax #-}++module ALife.Creatur.Genetics.CodeQC+  (+    test+  ) where++import ALife.Creatur.Genetics.Code+import ALife.Creatur.Genetics.CodeInternal+import Data.Eq.Unicode ((≡))+import Data.Ord.Unicode ((≤))+import Data.List (nub)+import Test.Framework as TF (Test, testGroup)+import Test.Framework.Providers.QuickCheck2 (testProperty)+import Test.QuickCheck (Arbitrary, Gen, Property, arbitrary, property, +  sized, vectorOf)++-- guaranteed not to have multiple values for the same code (but might have+-- multiple codes for the same value)+data TestCode = TestCode (Code Char Bool) deriving Show++sizedArbTestCode ∷ Int → Gen TestCode+sizedArbTestCode n = do+  cs ← vectorOf n arbitrary+  xs ← vectorOf n arbitrary+  return $ TestCode $ Code n $ zip cs (nub xs)++instance Arbitrary TestCode where+  arbitrary = sized sizedArbTestCode++prop_encoding_round_trippable ∷ TestCode → Char → Property+prop_encoding_round_trippable (TestCode g) c =+  property $ maybe Nothing (decode g) (encode g c) ≡ c'+    where c' = if c `elem` cs then Just c else Nothing+          cs = map fst $ cTable g++-- guaranteed not to have multiple values for the same code, or multiple codes+-- for the same value+data TestCode2 = TestCode2 (Code Char Bool) deriving Show++sizedArbTestCode2 ∷ Int → Gen TestCode2+sizedArbTestCode2 n = do+  cs ← vectorOf n arbitrary+  xs ← vectorOf n arbitrary+  return $ TestCode2 $ Code n $ zip (nub cs) (nub xs)++instance Arbitrary TestCode2 where+  arbitrary = sized sizedArbTestCode2++prop_decoding_round_trippable ∷ TestCode2 → [Bool] → Property+prop_decoding_round_trippable (TestCode2 g) x =+  property $ maybe Nothing (encode g) (decode g x) ≡ x'+    where x' = if x `elem` xs then Just x else Nothing+          xs = map snd $ cTable g++data TestParms = TestParms String deriving Show++sizedTestParms ∷ Int → Gen TestParms+sizedTestParms n = do+  let n' = 2 + min 8 n -- keep number of values low to speed up tests+  let values = take n' ['a' .. 'z']+  return $ TestParms values++instance Arbitrary TestParms where+  arbitrary = sized sizedTestParms+  +prop_gray_code_is_efficient ∷ TestParms → Property+prop_gray_code_is_efficient (TestParms values) = +  property $ 2 ^ (nBits - 1) < nValues && nValues ≤ 2 ^ nBits+    where g = mkGrayCode values+          nValues = length values+          nBits = (length . snd . head . cTable) g++test ∷ Test+test = testGroup "QuickCheck ALife.Creatur.Genetics.CodeQC"+  [+    testProperty "prop_encoding_round_trippable"+      prop_encoding_round_trippable,+    testProperty "prop_decoding_round_trippable"+      prop_decoding_round_trippable,+    testProperty "prop_gray_code_is_efficient"+      prop_gray_code_is_efficient+  ]++
+ test/ALife/Creatur/Genetics/CrossoverQC.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE UnicodeSyntax #-}++module ALife.Creatur.Genetics.CrossoverQC+  (+    test+  ) where++import ALife.Creatur.Genetics.Crossover (crossover, cutAndSplice)+import Data.Eq.Unicode ((≡))+import Test.Framework as TF (Test, testGroup)+import Test.Framework.Providers.QuickCheck2 (testProperty)+import Test.QuickCheck (Property, property)++prop_cutAndSplice_preserves_sum_of_lengths ∷+  Int → Int → (String, String) → Property+prop_cutAndSplice_preserves_sum_of_lengths n m (as, bs) =+  property $ length as' + length bs' ≡ length as + length bs+    where (as', bs') = cutAndSplice n m (as, bs)++prop_crossover_preserves_sum_of_lengths ∷ Int → (String, String) → Property+prop_crossover_preserves_sum_of_lengths n (as, bs) =+  property $ length as' + length bs' ≡ length as + length bs+    where (as', bs') = crossover n (as, bs)++test ∷ Test+test = testGroup "QuickCheck ALife.Creatur.Genetics.CrossoverQC"+  [+    testProperty "prop_cutAndSplice_preserves_sum_of_lengths"+      prop_cutAndSplice_preserves_sum_of_lengths,+    testProperty "prop_crossover_preserves_sum_of_lengths"+      prop_crossover_preserves_sum_of_lengths+  ]
+ test/ALife/Creatur/UtilQC.hs view
@@ -0,0 +1,98 @@+{-# LANGUAGE UnicodeSyntax #-}++module ALife.Creatur.UtilQC+  (+    test+  ) where++import ALife.Creatur.Util (cropRect, cropSquare, isqrt, replaceElement, +  safeReplaceElement, shuffle)+import Control.Monad.Random (evalRand, mkStdGen)+import Data.Eq.Unicode ((≡))+import Data.Ix (range)+import Data.List (sort)+import Data.Ord.Unicode ((≤))+import Test.Framework as TF (Test, testGroup)+import Test.Framework.Providers.QuickCheck2 (testProperty)+import Test.QuickCheck (Property, property)++-- prop_constrain_obeys_bounds ∷ (Int, Int) → Int → Property+-- prop_constrain_obeys_bounds (a, b) x = property $ a ≤ x' && x' ≤ b+--  where x' = constrain (a, b) x++-- prop_constrain_works ∷ (Int, Int) → Int → Property+-- prop_constrain_works (a, b) x = property $ x' ≡ x || x ≤ a || b ≤ x || b < a+--  where x' = constrain (a, b) x++prop_cropRect_returns_correct_size ∷+  (Int, Int) → (Int, Int) → String → Int → Property+prop_cropRect_returns_correct_size (a,b) (c, d) xs k =+    property $ length xs' ≡ expectedSize || length xs < expectedSize+  where expectedSize = length is'+        is = range ((0,0),(lastRow,lastCol))+        is' = filter wanted is+        wanted (i,j) = a ≤ i && i ≤ c && b ≤ j && j ≤ d+        lastRow = if k ≡ 0 then -1 else (length xs `div` k) - 1 + delta+        lastCol = constrain (-1,length xs - 1) (k - 1)+        delta = if length xs `mod` k ≡ 0 then 0 else 1 --add partial row+        xs' = cropRect (a, b) (c, d) xs k++-- Warning: If b < a, returns either a or x+constrain ∷ Ord a ⇒ (a, a) → a → a+constrain (a,b) x | x < a     = a+                  | x > b     = b+                  | otherwise = x++prop_cropSquare_returns_correct_size ∷ Int → String → Property+prop_cropSquare_returns_correct_size n xs =+    property $ length xs' ≡ expectedSize+  where expectedRows = min n ((isqrt . length) xs)+        expectedSize = if n < 0 then 0 else expectedRows*expectedRows+        xs' = cropSquare n xs++prop_replaceElement_changes_the_right_element ∷ String → Int → Char → Property+prop_replaceElement_changes_the_right_element cs i c =+  property+    $+      if 0 ≤ i && i < length cs+        then replaceElement cs i c !! i ≡ c+        else replaceElement cs i c ≡ cs++prop_safeReplaceElement_doesnt_change_length ∷ String → Int → Char → Property+prop_safeReplaceElement_doesnt_change_length cs i c =+  property $ length cs ≡ length (safeReplaceElement cs i c)++prop_safeReplaceElement_changes_the_right_element ∷+  String → Int → Char → Property+prop_safeReplaceElement_changes_the_right_element cs i c =+  property+    $+      if 0 ≤ i && i < length cs+        then cs' !! i ≡ c+        else cs' ≡ cs+  where cs' = safeReplaceElement cs i c++prop_shuffle_doesnt_change_elements ∷ String → Int → Property+prop_shuffle_doesnt_change_elements xs k = property $ sort xs ≡ sort xs'+  where xs' = evalRand (shuffle xs) (mkStdGen k)++test ∷ Test+test = testGroup "QuickCheck ALife.Creatur.UtilQC"+  [+--    testProperty "prop_constrain_obeys_bounds"+--      prop_constrain_obeys_bounds,+--    testProperty "prop_constrain_works"+--      prop_constrain_works,+    testProperty "prop_safeReplaceElement_changes_the_right_element"+      prop_safeReplaceElement_changes_the_right_element,+    testProperty "prop_safeReplaceElement_doesnt_change_length"+      prop_safeReplaceElement_doesnt_change_length,+    testProperty "prop_cropRect_returns_correct_size"+      prop_cropRect_returns_correct_size,+    testProperty "prop_cropSquare_returns_correct_size"+      prop_cropSquare_returns_correct_size,+    testProperty "prop_replaceElement_changes_the_right_element"+      prop_replaceElement_changes_the_right_element,+    testProperty "prop_shuffle_doesnt_change_elements"+      prop_shuffle_doesnt_change_elements+  ]
+ test/Main.hs view
@@ -0,0 +1,21 @@+{-# LANGUAGE UnicodeSyntax #-}+module Main where++import ALife.Creatur.Database.FileSystemQC (test)+import ALife.Creatur.UtilQC (test)+import ALife.Creatur.Genetics.CodeQC (test)+import ALife.Creatur.Genetics.CrossoverQC (test)++import Test.Framework as TF (defaultMain, Test)++tests ∷ [TF.Test]+tests = +  [ +    ALife.Creatur.Database.FileSystemQC.test,+    ALife.Creatur.UtilQC.test,+    ALife.Creatur.Genetics.CodeQC.test,+    ALife.Creatur.Genetics.CrossoverQC.test+  ]++main ∷ IO ()+main = defaultMain tests