summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--nerine.cabal5
-rw-r--r--package.yaml1
-rw-r--r--src/Genome.hs69
-rw-r--r--test/GenomeTest.hs72
-rw-r--r--test/Main.hs2
5 files changed, 149 insertions, 0 deletions
diff --git a/nerine.cabal b/nerine.cabal
index 52b1c4a..7592604 100644
--- a/nerine.cabal
+++ b/nerine.cabal
@@ -25,6 +25,7 @@ source-repository head
library
exposed-modules:
+ Genome
Lib
Mind
other-modules:
@@ -36,6 +37,7 @@ library
ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints
build-depends:
base >=4.7 && <5
+ , random
, tasty
, tasty-hunit
default-language: Haskell2010
@@ -52,6 +54,7 @@ executable nerine-exe
build-depends:
base >=4.7 && <5
, nerine
+ , random
, tasty
, tasty-hunit
default-language: Haskell2010
@@ -60,6 +63,7 @@ test-suite nerine-test
type: exitcode-stdio-1.0
main-is: Main.hs
other-modules:
+ GenomeTest
MindTest
Paths_nerine
autogen-modules:
@@ -70,6 +74,7 @@ test-suite nerine-test
build-depends:
base >=4.7 && <5
, nerine
+ , random
, tasty
, tasty-hunit
default-language: Haskell2010
diff --git a/package.yaml b/package.yaml
index bd8c0f2..f0d605b 100644
--- a/package.yaml
+++ b/package.yaml
@@ -23,6 +23,7 @@ dependencies:
- base >= 4.7 && < 5
- tasty
- tasty-hunit
+- random
ghc-options:
- -Wall
diff --git a/src/Genome.hs b/src/Genome.hs
new file mode 100644
index 0000000..4fb063c
--- /dev/null
+++ b/src/Genome.hs
@@ -0,0 +1,69 @@
+module Genome
+ ( Gene (..)
+ , Genome (..)
+ , mutateGeneSource
+ , mutateGeneSink
+ , mutateGeneWeight
+ , mutateGene
+ ) where
+
+
+import Mind (NeuronIndex (..))
+import System.Random
+
+
+data Gene = Gene { source :: NeuronIndex, sink :: NeuronIndex, weight :: Float } deriving (Eq, Show)
+data Genome = Genome { numInput :: Int, numInternal :: Int, numOutput :: Int, genes :: [Gene] } deriving (Eq, Show)
+
+
+-- choose a random list element
+randomChoice :: RandomGen a => [b] -> a -> (b, a)
+randomChoice xs r =
+ let (index, r') = randomR (0, length xs - 1) r
+ in (xs !! index, r')
+
+
+-- pick a new random source for the gene
+mutateGeneSource :: RandomGen a => Genome -> Gene -> a -> (Gene, a)
+mutateGeneSource genome g r =
+ let
+ nInput = numInput genome
+ nInternal = numInternal genome
+ index :: Int
+ (index, r') = randomR (0, nInput+nInternal-1) r
+ source' =
+ if index < nInput
+ then Input index
+ else Internal $ index - nInput
+ in ( g { source = source' }, r')
+
+
+-- pick a new random sink for the gene
+mutateGeneSink :: RandomGen a => Genome -> Gene -> a -> (Gene, a)
+mutateGeneSink genome g r =
+ let
+ nInternal = numInternal genome
+ nOutput = numOutput genome
+ index :: Int
+ (index, r') = randomR (0, nInternal+nOutput-1) r
+ sink' = if index < nInternal
+ then Internal index
+ else Output $ index - nInternal
+ in ( g { sink = sink' }, r' )
+
+
+-- pick a new random weight, in the range (-w, w)
+-- where w is twice the absolute value of the current weight
+mutateGeneWeight :: RandomGen a => Genome -> Gene -> a -> (Gene, a)
+mutateGeneWeight _ g r =
+ let
+ w = 2 * (abs $ weight g)
+ (weight', r') = randomR (negate w, w) r
+ in ( g { weight = weight' }, r' )
+
+
+-- randomly mutate gene (modify source, sink, or weight
+mutateGene :: RandomGen a => Genome -> Gene -> a -> (Gene, a)
+mutateGene genome g r =
+ let (f, r') = randomChoice [mutateGeneSource, mutateGeneSink, mutateGeneWeight] r
+ in f genome g r'
diff --git a/test/GenomeTest.hs b/test/GenomeTest.hs
new file mode 100644
index 0000000..0a27a94
--- /dev/null
+++ b/test/GenomeTest.hs
@@ -0,0 +1,72 @@
+module GenomeTest (suite) where
+
+import Test.Tasty
+import Test.Tasty.HUnit
+import qualified Mind as M
+import Genome
+import System.Random
+
+suite :: TestTree
+suite = testGroup "genome tests" $
+ [ mutationTests
+ ]
+
+
+mutationTests :: TestTree
+mutationTests = testGroup "mutations" $
+ [ testCase "mutating the source of a gene" $
+ let
+ rand = mkStdGen 1 -- randomR generates sequence (1, 2, 0, ...)
+ genome = Genome { numInput = 2, numInternal = 1, numOutput = 5, genes = [] }
+ sourceGene = Gene { source = M.Input 0, sink = M.Output 1, weight = 4 }
+ mutatedGenes = fst $ foldl
+ (\(list, r) g ->
+ let (g', r') = mutateGeneSource genome g r
+ in (list ++ [g'], r')
+ )
+ ([], rand)
+ (replicate 3 sourceGene)
+ in
+ mutatedGenes @?=
+ [ Gene { source = M.Input 1, sink = M.Output 1, weight = 4 }
+ , Gene { source = M.Internal 0, sink = M.Output 1, weight = 4 }
+ , Gene { source = M.Input 0, sink = M.Output 1, weight = 4 }
+ ]
+ , testCase "mutating the sink of a gene" $
+ let
+ rand = mkStdGen 1 -- randomR generates sequence (1, 2, 0, ...)
+ genome = Genome { numInput = 2, numInternal = 1, numOutput = 2, genes = [] }
+ sourceGene = Gene { source = M.Input 0, sink = M.Output 1, weight = 4 }
+ mutatedGenes = fst $ foldl
+ (\(list, r) g ->
+ let (g', r') = mutateGeneSink genome g r
+ in (list ++ [g'], r')
+ )
+ ([], rand)
+ (replicate 3 sourceGene)
+ in
+ mutatedGenes @?=
+ [ Gene { source = M.Input 0, sink = M.Output 0, weight = 4 }
+ , Gene { source = M.Input 0, sink = M.Output 1, weight = 4 }
+ , Gene { source = M.Input 0, sink = M.Internal 0, weight = 4 }
+ ]
+ , testCase "mutating the weight of a gene" $
+ let
+ rand = mkStdGen 0 -- randomR generates sequence (7.572357,-1.4116564,-7.2413177, ...)
+ genome = Genome { numInput = 2, numInternal = 1, numOutput = 2, genes = [] }
+ sourceGene = Gene { source = M.Input 0, sink = M.Output 1, weight = 4 }
+ mutatedGenes = fst $ foldl
+ (\(list, r) g ->
+ let (g', r') = mutateGeneWeight genome g r
+ in (list ++ [g'], r')
+ )
+ ([], rand)
+ (replicate 3 sourceGene)
+ in
+ mutatedGenes @?=
+ [ Gene { source = M.Input 0, sink = M.Output 1, weight = 7.572357}
+ , Gene { source = M.Input 0, sink = M.Output 1, weight = -1.4116564 }
+ , Gene { source = M.Input 0, sink = M.Internal 1, weight = -7.2413177 }
+ ]
+
+ ]
diff --git a/test/Main.hs b/test/Main.hs
index 8847a07..907bbdd 100644
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -3,8 +3,10 @@ module Main (main) where
import Test.Tasty
import qualified MindTest
+import qualified GenomeTest
main :: IO ()
main = defaultMain $ testGroup "all tests" $
[ MindTest.suite
+ , GenomeTest.suite
]