packages feed

neet 0.1.0.0 → 0.2.0.0

raw patch · 8 files changed

+554/−195 lines, 8 filesdep +cerealdep +paralleldep +randomPVP ok

version bump matches the API change (PVP)

Dependencies added: cereal, parallel, random

API changes (from Hackage documentation)

- Neet.Network: yHint :: Neuron -> Rational
- Neet.Parameters: defDP :: DistParams
- Neet.Parameters: smallParams :: Parameters
- Neet.Population: popParamsS :: Population -> Parameters
- Neet.Population: psParamsS :: PopSettings -> Parameters
+ Neet.Examples.XOR: xorExperiment :: IO ()
+ Neet.Genome: GS :: (Genome -> score) -> (score -> Double) -> (score -> Bool) -> GenScorer score
+ Neet.Genome: data GenScorer score
+ Neet.Genome: fitnessFunction :: GenScorer score -> score -> Double
+ Neet.Genome: gScorer :: GenScorer score -> Genome -> score
+ Neet.Genome: getInnoId :: InnoId -> Int
+ Neet.Genome: getNodeId :: NodeId -> Int
+ Neet.Genome: instance Constructor C1_0ConnGene
+ Neet.Genome: instance Constructor C1_0Genome
+ Neet.Genome: instance Constructor C1_0NodeGene
+ Neet.Genome: instance Constructor C1_0NodeType
+ Neet.Genome: instance Constructor C1_1NodeType
+ Neet.Genome: instance Constructor C1_2NodeType
+ Neet.Genome: instance Datatype D1ConnGene
+ Neet.Genome: instance Datatype D1Genome
+ Neet.Genome: instance Datatype D1NodeGene
+ Neet.Genome: instance Datatype D1NodeType
+ Neet.Genome: instance Generic ConnGene
+ Neet.Genome: instance Generic Genome
+ Neet.Genome: instance Generic NodeGene
+ Neet.Genome: instance Generic NodeType
+ Neet.Genome: instance Selector S1_0_0ConnGene
+ Neet.Genome: instance Selector S1_0_0Genome
+ Neet.Genome: instance Selector S1_0_0NodeGene
+ Neet.Genome: instance Selector S1_0_1ConnGene
+ Neet.Genome: instance Selector S1_0_1Genome
+ Neet.Genome: instance Selector S1_0_1NodeGene
+ Neet.Genome: instance Selector S1_0_2ConnGene
+ Neet.Genome: instance Selector S1_0_2Genome
+ Neet.Genome: instance Selector S1_0_3ConnGene
+ Neet.Genome: instance Selector S1_0_4ConnGene
+ Neet.Genome: instance Serialize ConnGene
+ Neet.Genome: instance Serialize Genome
+ Neet.Genome: instance Serialize NodeGene
+ Neet.Genome: instance Serialize NodeId
+ Neet.Genome: instance Serialize NodeType
+ Neet.Genome: printGenome :: Genome -> IO ()
+ Neet.Genome: sparseConn :: MonadRandom m => MutParams -> Int -> Int -> Int -> m Genome
+ Neet.Genome: validateGenome :: Genome -> Maybe [String]
+ Neet.Genome: winCriteria :: GenScorer score -> score -> Bool
+ Neet.Network: neurType :: Neuron -> NodeType
+ Neet.Network: pushThrough :: Network -> [Double] -> [Double]
+ Neet.Network: yHeight :: Neuron -> Rational
+ Neet.Parameters: MutParams :: Double -> Double -> Double -> Double -> Double -> Double -> Bool -> Double -> Double -> MutParams
+ Neet.Parameters: data MutParams
+ Neet.Parameters: defDistParams :: DistParams
+ Neet.Parameters: defMutParams :: MutParams
+ Neet.Parameters: defMutParamsS :: MutParams
+ Neet.Parameters: instance Show MutParams
+ Neet.Parameters: mutParams :: Parameters -> MutParams
+ Neet.Parameters: mutParamsS :: Parameters -> MutParams
+ Neet.Parameters: recurrencies :: MutParams -> Bool
+ Neet.Population: SpecId :: Int -> SpecId
+ Neet.Population: newtype SpecId
+ Neet.Population: popGen :: Population -> Int
+ Neet.Population: sparse :: PopSettings -> Maybe Int
+ Neet.Population: speciesCount :: Population -> Int
+ Neet.Population: validatePopulation :: Population -> Maybe [String]
+ Neet.Species: TR :: MultiMap Double Genome -> !SpecScore -> !Double -> !(Maybe Genome) -> TestResult
+ Neet.Species: data TestResult
+ Neet.Species: maxDist :: Parameters -> Species -> Double
+ Neet.Species: trAdj :: TestResult -> !Double
+ Neet.Species: trSS :: TestResult -> !SpecScore
+ Neet.Species: trScores :: TestResult -> MultiMap Double Genome
+ Neet.Species: trSol :: TestResult -> !(Maybe Genome)
+ Neet.Species: validateSpecies :: Species -> Maybe [String]
- Neet.Examples.XOR: andFit :: Genome -> Double
+ Neet.Examples.XOR: andFit :: GenScorer [Double]
- Neet.Examples.XOR: orFit :: Genome -> Double
+ Neet.Examples.XOR: orFit :: GenScorer [Double]
- Neet.Examples.XOR: xorFit :: Genome -> Double
+ Neet.Examples.XOR: xorFit :: GenScorer [Double]
- Neet.Genome: Genome :: Map NodeId NodeGene -> Map InnoId ConnGene -> NodeId -> Genome
+ Neet.Genome: Genome :: IntMap NodeGene -> IntMap ConnGene -> NodeId -> Genome
- Neet.Genome: breed :: (MonadRandom m, MonadFresh InnoId m) => Parameters -> Map ConnSig InnoId -> Genome -> Genome -> m (Map ConnSig InnoId, Genome)
+ Neet.Genome: breed :: (MonadRandom m, MonadFresh InnoId m) => MutParams -> Map ConnSig InnoId -> Genome -> Genome -> m (Map ConnSig InnoId, Genome)
- Neet.Genome: connGenes :: Genome -> Map InnoId ConnGene
+ Neet.Genome: connGenes :: Genome -> IntMap ConnGene
- Neet.Genome: crossover :: MonadRandom m => Parameters -> Genome -> Genome -> m Genome
+ Neet.Genome: crossover :: MonadRandom m => MutParams -> Genome -> Genome -> m Genome
- Neet.Genome: fullConn :: MonadRandom m => Parameters -> Int -> Int -> m Genome
+ Neet.Genome: fullConn :: MonadRandom m => MutParams -> Int -> Int -> m Genome
- Neet.Genome: mutate :: (MonadRandom m, MonadFresh InnoId m) => Parameters -> Map ConnSig InnoId -> Genome -> m (Map ConnSig InnoId, Genome)
+ Neet.Genome: mutate :: (MonadRandom m, MonadFresh InnoId m) => MutParams -> Map ConnSig InnoId -> Genome -> m (Map ConnSig InnoId, Genome)
- Neet.Genome: nodeGenes :: Genome -> Map NodeId NodeGene
+ Neet.Genome: nodeGenes :: Genome -> IntMap NodeGene
- Neet.Network: Network :: [NodeId] -> [NodeId] -> Map NodeId Neuron -> Int -> Network
+ Neet.Network: Network :: [NodeId] -> [NodeId] -> IntMap Neuron -> Int -> Network
- Neet.Network: Neuron :: Double -> Map NodeId Double -> Rational -> Neuron
+ Neet.Network: Neuron :: Double -> IntMap Double -> Rational -> NodeType -> Neuron
- Neet.Network: connections :: Neuron -> Map NodeId Double
+ Neet.Network: connections :: Neuron -> IntMap Double
- Neet.Network: netState :: Network -> Map NodeId Neuron
+ Neet.Network: netState :: Network -> IntMap Neuron
- Neet.Network: stepNeuron :: Map NodeId Double -> Neuron -> Neuron
+ Neet.Network: stepNeuron :: IntMap Double -> Neuron -> Neuron
- Neet.Parameters: Parameters :: Double -> Double -> Double -> Double -> Double -> Double -> Int -> Double -> DistParams -> Maybe Int -> Double -> Parameters
+ Neet.Parameters: Parameters :: MutParams -> MutParams -> Int -> DistParams -> Maybe Int -> Parameters
- Neet.Parameters: addConnRate :: Parameters -> Double
+ Neet.Parameters: addConnRate :: MutParams -> Double
- Neet.Parameters: addNodeRate :: Parameters -> Double
+ Neet.Parameters: addNodeRate :: MutParams -> Double
- Neet.Parameters: disableChance :: Parameters -> Double
+ Neet.Parameters: disableChance :: MutParams -> Double
- Neet.Parameters: mutWeightRate :: Parameters -> Double
+ Neet.Parameters: mutWeightRate :: MutParams -> Double
- Neet.Parameters: newWeightRate :: Parameters -> Double
+ Neet.Parameters: newWeightRate :: MutParams -> Double
- Neet.Parameters: noCrossover :: Parameters -> Double
+ Neet.Parameters: noCrossover :: MutParams -> Double
- Neet.Parameters: pertAmount :: Parameters -> Double
+ Neet.Parameters: pertAmount :: MutParams -> Double
- Neet.Parameters: weightRange :: Parameters -> Double
+ Neet.Parameters: weightRange :: MutParams -> Double
- Neet.Population: PS :: Int -> Int -> Int -> Parameters -> Parameters -> PopSettings
+ Neet.Population: PS :: Int -> Int -> Int -> Parameters -> Maybe Int -> PopSettings
- Neet.Population: Population :: Int -> !(Map SpecId Species) -> !Double -> !Genome -> !SpecId -> !PopContext -> !SpecId -> Parameters -> Parameters -> Population
+ Neet.Population: Population :: Int -> !(Map SpecId Species) -> !Double -> !Genome -> !SpecId -> !PopContext -> !SpecId -> Parameters -> Int -> Population
- Neet.Population: trainN :: Int -> (Genome -> Double) -> Population -> Population
+ Neet.Population: trainN :: Int -> GenScorer a -> Population -> Population
- Neet.Population: trainOnce :: (Genome -> Double) -> Population -> Population
+ Neet.Population: trainOnce :: GenScorer a -> Population -> (Population, Maybe Genome)
- Neet.Population: trainUntil :: Double -> Int -> (Genome -> Double) -> Population -> (Population, Int)
+ Neet.Population: trainUntil :: Int -> GenScorer a -> Population -> (Population, Maybe (Genome, Int))
- Neet.Species: SpecScore :: Double -> Genome -> SpecScore
+ Neet.Species: SpecScore :: !Double -> !Genome -> SpecScore
- Neet.Species: bestGen :: SpecScore -> Genome
+ Neet.Species: bestGen :: SpecScore -> !Genome
- Neet.Species: bestScore :: SpecScore -> Double
+ Neet.Species: bestScore :: SpecScore -> !Double
- Neet.Species: runFitTest :: (Genome -> Double) -> Species -> (MultiMap Double Genome, SpecScore, Double)
+ Neet.Species: runFitTest :: GenScorer a -> Species -> TestResult

Files

neet.cabal view
@@ -2,7 +2,7 @@ -- see http://haskell.org/cabal/users-guide/  name:                neet-version:             0.1.0.0+version:             0.2.0.0 synopsis:            A NEAT library for Haskell -- description:          homepage:            https://github.com/raymoo/NEET@@ -23,12 +23,13 @@  library   exposed-modules:     Neet.Genome, Control.Monad.Fresh.Class, Neet.Parameters, Neet.Network,-                       Neet.Species, Neet.Population, Neet.Examples.XOR+                       Neet.Species, Neet.Population, Neet.Examples.XOR, Neet   -- other-modules:          other-extensions:    FunctionalDependencies, MultiParamTypeClasses   build-depends:       base >=4.7 && <4.8, MonadRandom >=0.4 && <0.5,                        containers >= 0.5 && < 0.6, multimap >= 1.2 && < 1.3,-                       transformers == 0.4.*, graphviz == 2999.17.*+                       transformers == 0.4.*, graphviz == 2999.17.*, cereal == 0.4.*,+                       random == 1.1.*, parallel == 3.2.*   hs-source-dirs:      src   default-language:    Haskell2010 
+ src/Neet.hs view
@@ -0,0 +1,39 @@+{-+Copyright (C) 2015 Leon Medvinsky++This program is free software; you can redistribute it and/or+modify it under the terms of the GNU General Public License+as published by the Free Software Foundation; either version 3+of the License, or (at your option) any later version.++This program is distributed in the hope that it will be useful,+but WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the+GNU General Public License for more details.++You should have received a copy of the GNU General Public License+along with this program; if not, write to the Free Software+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.+-}++{-|+Module      : Neet+Description : Reexport convenience module+Copyright   : (c) Leon Medvinsky, 2015++License     : GPL-3+Maintainer  : lmedvinsky@hotmail.com+Stability   : experimental+Portability : ghc+-}++module Neet (module Genome+            , module Population+            , module Parameters+            , module Network+            ) where++import Neet.Genome as Genome+import Neet.Population as Population+import Neet.Parameters as Parameters+import Neet.Network as Network
src/Neet/Examples/XOR.hs view
@@ -27,13 +27,19 @@ Portability : ghc -} -module Neet.Examples.XOR (xorFit, andFit, orFit) where+module Neet.Examples.XOR (xorFit, andFit, orFit, xorExperiment) where  -import Neet.Genome-import Neet.Network+import Neet+import Neet.Species +import qualified Data.Map.Strict as M +import System.Random++import Data.List (intercalate)++ boolQuestions :: [[Double]] boolQuestions = [ [0, 0]                 , [0, 1]@@ -41,30 +47,77 @@                 , [1, 1]                 ] -xorAnswers :: [Double]-xorAnswers = [0, 1, 1, 0]+xorAnswers :: [Bool]+xorAnswers = [False, True, True, False] -sampleFit :: [[Double]] -> [Double] -> Genome -> Double-sampleFit questions answers g = (fromIntegral (length answers) - sumDiffs)**2-  where net = mkPhenotype g-        try samp = head . getOutput $ snapshot net samp-        responses = map try questions-        sumDiffs = sum $ zipWith (\x y -> abs (x - y)) responses answers+sampleFit :: [[Double]] -> [Bool] -> GenScorer [Double]+sampleFit questions answers = GS intermed ff criteria+  where intermed g = map try questions+          where try samp = head $ pushThrough net samp+                net = mkPhenotype g+        ff ds = (fromIntegral (length answers) - sumDiffs)**2+          where sumDiffs = sum $ zipWith (\x y -> abs (x - y)) ds binarized+        binarized = map (\b -> if b then 1 else 0) answers+        bounds = map (\b -> if b then (>0.5) else (<0.5)) answers+        criteria ds = and $ zipWith id bounds ds -xorFit :: Genome -> Double+xorFit :: GenScorer [Double] xorFit = sampleFit boolQuestions xorAnswers -andAnswers :: [Double]-andAnswers = [0, 0, 0, 1]+andAnswers :: [Bool]+andAnswers = [False, False, False, True]  -andFit :: Genome -> Double+andFit :: GenScorer [Double] andFit = sampleFit boolQuestions andAnswers  -orAnswers :: [Double]-orAnswers = [0, 1, 1, 1]+orAnswers :: [Bool]+orAnswers = [False, True, True, True]  -orFit :: Genome -> Double+orFit :: GenScorer [Double] orFit = sampleFit boolQuestions orAnswers+++-- | Automated XOR experiment+xorExperiment :: IO ()+xorExperiment = do+  putStrLn $ "XOR Input list is: " ++ show boolQuestions+  putStrLn "Press Enter to start learning"+  _ <- getLine+  putStrLn "Running XOR experiment with 150 population and default parameters"+  seed <- randomIO+  let pop = newPop seed (PS 150 2 1 defParams (Just 2))+  (pop', sol) <- xorLoop pop+  printInfo pop'+  putStrLn $ "Solution found in generation " ++ show (popGen pop')+  let score = gScorer xorFit sol+  putStrLn $ "\nOutputs to XOR inputs are: " ++ show score+  putStrLn $ "Fitness (Out of 16): " ++ show (fitnessFunction xorFit score)+  putStrLn "\nPress Enter to view network"+  _ <- getLine+  renderGenome sol+++mkSpecInfo :: Population -> String+mkSpecInfo pop = intercalate ", " infos+  where infos = map (\((SpecId k), sp) -> "S" ++ show k ++ " P" ++ show (specSize sp)) ass+        ass = M.toList $ popSpecs pop+++xorLoop :: Population -> IO (Population, Genome)+xorLoop pop = do+  printInfo pop+  let (pop', mg) = trainOnce xorFit pop+  case mg of+   Nothing -> xorLoop pop'+   Just g -> return (pop',g)+     ++printInfo :: Population -> IO ()+printInfo pop = do+  putStrLn $ "Generation " ++ show (popGen pop)+  putStrLn $ "Species: " ++ mkSpecInfo pop+  putStrLn $ "High Score: " ++ show (popBScore pop)+  putStrLn ""
src/Neet/Genome.hs view
@@ -31,6 +31,8 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DefaultSignatures #-}  module Neet.Genome ( -- * Genes                      NodeId(..)@@ -43,25 +45,38 @@                    , Genome(..)                      -- ** Construction                    , fullConn+                   , sparseConn                      -- ** Breeding                    , mutate                    , crossover                    , breed                      -- ** Distance                    , distance+                     -- ** Fitness+                   , GenScorer(..)                      -- ** Visualization                    , renderGenome+                   , printGenome+                     -- ** Debugging+                   , validateGenome                    ) where  import Control.Applicative import Control.Monad import Control.Monad.Random+import Control.Arrow (first)+ import Data.Map.Strict (Map) import qualified Data.Traversable as T import qualified Data.Map.Strict as M +import qualified Data.IntSet as IS+ import qualified Data.Set as S +import qualified Data.IntMap as IM+import Data.IntMap (IntMap)+ import Data.Maybe  import Control.Monad.Fresh.Class@@ -70,23 +85,32 @@ import Data.GraphViz import Data.GraphViz.Attributes.Complete +import GHC.Generics (Generic)+import Data.Serialize (Serialize)++import Text.Printf+ -- | The IDs node genes use to refer to nodes.-newtype NodeId = NodeId Int-               deriving (Show, Eq, Ord, PrintDot)+newtype NodeId = NodeId { getNodeId :: Int }+               deriving (Show, Eq, Ord, PrintDot, Serialize)   -- | Types of nodes data NodeType = Input | Hidden | Output-              deriving (Show, Eq)+              deriving (Show, Eq, Generic) +instance Serialize NodeType + -- | Node genes data NodeGene = NodeGene { nodeType :: NodeType                          , yHint :: Rational -- ^ A hint for recurrency                          }-              deriving (Show)+              deriving (Show, Generic) +instance Serialize NodeGene + -- | Connection genes data ConnGene = ConnGene { connIn :: NodeId                          , connOut :: NodeId@@ -94,47 +118,73 @@                          , connEnabled :: Bool                          , connRec :: Bool -- ^ A hint for recurrency                          }-              deriving (Show)+              deriving (Show, Generic) +instance Serialize ConnGene + -- | Innovation IDs-newtype InnoId = InnoId Int+newtype InnoId = InnoId { getInnoId :: Int }                deriving (Show, Eq, Ord)   -- | A NEAT genome. The innovation numbers are stored in here, and not the genes, -- to prevent data duplication. data Genome =-  Genome { nodeGenes :: Map NodeId NodeGene-         , connGenes :: Map InnoId ConnGene+  Genome { nodeGenes :: IntMap NodeGene+         , connGenes :: IntMap ConnGene          , nextNode :: NodeId          }-  deriving (Show)+  deriving (Show, Generic)  +instance Serialize Genome++ -- | Takes the number of inputs, the number of outputs, and gives a genome with -- the inputs fully connected to the outputs with random weights. The order of -- the connections are deterministic, so when generating a population, you -- can just start the innovation number at (iSize + 1) * oSize, since the network -- includes an additional input for the bias.-fullConn :: MonadRandom m => Parameters -> Int -> Int -> m Genome-fullConn Parameters{..} iSize oSize = do+fullConn :: MonadRandom m => MutParams -> Int -> Int -> m Genome+fullConn MutParams{..} iSize oSize = do   let inCount = iSize + 1-      inIDs = map NodeId [1..inCount]-      outIDs = map NodeId [inCount + 1..oSize + inCount]+      inIDs = [1..inCount]+      outIDs = [inCount + 1..oSize + inCount]       inputGenes = zip inIDs $ repeat (NodeGene Input 0)       outputGenes = zip outIDs $ repeat (NodeGene Output 1)-      nodeGenes = M.fromList $ inputGenes ++ outputGenes+      nodeGenes = IM.fromList $ inputGenes ++ outputGenes       nextNode = NodeId $ inCount + oSize + 1       nodePairs = (,) <$> inIDs <*> outIDs-  conns <- zipWith (\(inN, outN) w -> ConnGene inN outN w True False) nodePairs `liftM` getRandomRs (-weightRange,weightRange)-  let connGenes = M.fromList $ zip (map InnoId [1..]) conns+  conns <- zipWith (\(inN, outN) w -> ConnGene (NodeId inN) (NodeId outN) w True False)+           nodePairs `liftM` getRandomRs (-weightRange,weightRange)+  let connGenes = IM.fromList $ zip [1..] conns   return $ Genome{..}  +-- | Like 'fullConn', but with only some input-outputs connected. First integer+-- parameters is the max number of connections to start with.+sparseConn :: MonadRandom m => MutParams -> Int -> Int -> Int -> m Genome+sparseConn MutParams{..} cons iSize oSize = do+  let inCount = iSize + 1+      inIDs = [1..inCount]+      outIDs = [inCount + 1..oSize + inCount]+      inputGenes = zip inIDs $ repeat (NodeGene Input 0)+      outputGenes = zip outIDs $ repeat (NodeGene Output 1)+      nodeGenes = IM.fromList $ inputGenes ++ outputGenes+      nextNode = NodeId $ inCount + oSize + 1+      nodePairs = (,) <$> inIDs <*> outIDs+      idNodePairs = zip [1..] nodePairs+  conPairs <- replicateM cons (uniform idNodePairs)+  conns <- zipWith (\(inno,(inN, outN)) w -> (inno, ConnGene (NodeId inN) (NodeId outN) w True False))+           conPairs `liftM` getRandomRs (-weightRange, weightRange)+  let connGenes = IM.fromList conns+  return $ Genome{..}++ -- | Mutate the weights - perturb or make entirely new weights-mutateWeights :: MonadRandom m => Parameters -> Genome -> m Genome-mutateWeights Parameters{..} gen@Genome{..} = do+mutateWeights :: MonadRandom m => MutParams -> Genome -> m Genome+mutateWeights MutParams{..} gen@Genome{..} = do   roll <- getRandomR (0,1)   if roll > mutWeightRate     then return gen@@ -162,20 +212,20 @@  -- | Adds a single connection, updating the innovation context addConn :: MonadFresh InnoId m => ConnGene ->-           (Map ConnSig InnoId, Map InnoId ConnGene) ->-           m (Map ConnSig InnoId, Map InnoId ConnGene)+           (Map ConnSig InnoId, IntMap ConnGene) ->+           m (Map ConnSig InnoId, IntMap ConnGene) addConn conn (innos, conns) = case M.lookup siggy innos of-  Just inno -> return (innos, M.insert inno conn conns)+  Just inno -> return (innos, IM.insert (getInnoId inno) conn conns)   Nothing -> do-    newInno <- fresh-    return (M.insert siggy newInno innos, M.insert newInno conn conns)+    nI@(InnoId newInno) <- fresh+    return (M.insert siggy nI innos, IM.insert newInno conn conns)   where siggy = toConnSig conn   -- | Mutation of additional connection. 'Map' parameter is context of previous -- innovations. This could be global, or per species generation. mutateConn :: (MonadFresh InnoId m, MonadRandom m) =>-              Parameters -> Map ConnSig InnoId -> Genome -> m (Map ConnSig InnoId, Genome)+              MutParams -> Map ConnSig InnoId -> Genome -> m (Map ConnSig InnoId, Genome) mutateConn params innos g = do   roll <- getRandomR (0,1)   if roll > addConnRate params@@ -190,24 +240,29 @@         -- | Which connections are already filled up by genes. Value is a dummy         -- value because taken is only used in difference anyway.         taken :: Map ConnSig Bool-        taken = M.fromList . map (\c -> (toConnSig c, True)) . M.elems . connGenes $ g+        taken = M.fromList . map (\c -> (toConnSig c, True)) . IM.elems . connGenes $ g          -- | Whether a gene is an input gene         notInput (NodeGene Input _) = False         notInput _                  = True          -- | The genome's nodes, in an assoc list-        nodes = M.toList $ nodeGenes g+        nodes = IM.toList $ nodeGenes g          -- | Nodes that are not input         nonInputs = filter (notInput . snd) nodes          -- | Make a pair of 'ConnSig' and the recurrentness-        makePair (n1,g1) (n2,g2) = (ConnSig n1 n2, yHint g2 <= yHint g1)+        makePair (n1,g1) (n2,g2) = (ConnSig (NodeId n1) (NodeId n2), yHint g2 <= yHint g1)          -- | Possible input -> output pairs-        candidates = M.fromList $ makePair <$> nodes <*> nonInputs+        candidates = M.fromList $+                     if recurrencies params+                     then makePair <$> nodes <*> nonInputs+                     else filter nonRec $ makePair <$> nodes <*> nonInputs +        nonRec (_,reccy) = not reccy+         -- | Which pairs are not taken         allowed = M.toList $ M.difference candidates taken @@ -221,8 +276,8 @@         -- | Randomly chooses one of the available connections and creates a         -- gene for it         addRandConn :: (MonadRandom m, MonadFresh InnoId m) =>-                       Map ConnSig InnoId -> Map InnoId ConnGene ->-                       m (Map ConnSig InnoId, Map InnoId ConnGene)+                       Map ConnSig InnoId -> IntMap ConnGene ->+                       m (Map ConnSig InnoId, IntMap ConnGene)         addRandConn innos' conns = do           (ConnSig inNode outNode, recc) <- pickOne           w <- pickWeight@@ -232,7 +287,7 @@  -- | Mutation of additional node. mutateNode :: (MonadRandom m, MonadFresh InnoId m) =>-              Parameters -> Map ConnSig InnoId ->+              MutParams -> Map ConnSig InnoId ->               Genome -> m (Map ConnSig InnoId, Genome) mutateNode params innos g = do   roll <- getRandomR (0,1)@@ -241,8 +296,8 @@         nodes = nodeGenes g          -- | Pick one of the 'InnoId' 'ConnGene' pairs from conns-        pickConn :: MonadRandom m => m (InnoId, ConnGene)-        pickConn = uniform $ M.toList conns+        pickConn :: MonadRandom m => m (Int, ConnGene)+        pickConn = uniform $ IM.toList conns          -- | What will the new node's ID be         newId = nextNode g@@ -255,45 +310,45 @@         addNode :: MonadFresh InnoId m =>                    InnoId -> ConnGene -> m (Map ConnSig InnoId, Genome)         addNode inno gene = do-          let ConnSig inId outId = toConnSig gene+          let ConnSig (NodeId inId) (NodeId outId) = toConnSig gene                -- | Gene of the input node of this connection-              inGene = nodes M.! inId+              inGene = nodes IM.! inId                -- | Gene of the output node of this connection-              outGene = nodes M.! outId+              outGene = nodes IM.! outId                -- | The new node gene               newGene = NodeGene Hidden ((yHint inGene + yHint outGene) / 2)                -- | The new map of nodes, after inserting the new one-              newNodes = M.insert newId newGene nodes+              newNodes = IM.insert (getNodeId newId) newGene nodes                -- | The disabled version of the old connection               disabledConn = gene { connEnabled = False }                -- | The gene for the connection between the input and the new node-              backGene = ConnGene inId newId 1 True (connRec gene)+              backGene = ConnGene (NodeId inId) newId 1 True (connRec gene)                -- | The gene for the connection between the new node and the output-              forwardGene = ConnGene newId outId (connWeight gene) True (connRec gene)+              forwardGene = ConnGene newId (NodeId outId) (connWeight gene) True (connRec gene)                          (innos', newConns) <-             addConn backGene >=> addConn forwardGene $ (innos, conns)            return $ (innos', g { nodeGenes = newNodes-                              , connGenes = M.insert inno disabledConn newConns+                              , connGenes = IM.insert (getInnoId inno) disabledConn newConns                               , nextNode = newNextNode                               })          -- | Pick an available connection randomly and make a gene for it         addRandNode :: (MonadRandom m, MonadFresh InnoId m) => m (Map ConnSig InnoId, Genome)         addRandNode =-          pickConn >>= uncurry addNode+          pickConn >>= uncurry (addNode . InnoId)   -- | Mutates the genome, using the specified parameters and innovation context.-mutate :: (MonadRandom m, MonadFresh InnoId m) => Parameters -> Map ConnSig InnoId ->+mutate :: (MonadRandom m, MonadFresh InnoId m) => MutParams -> Map ConnSig InnoId ->           Genome -> m (Map ConnSig InnoId, Genome) mutate params innos g = do   g' <- mutateWeights params g@@ -301,18 +356,20 @@   -- | Super left biased merge -- loners on the right map don't get in-superLeft :: Ord k => (a -> b -> c) -> (a -> c) -> Map k a -> Map k b -> Map k c-superLeft comb mk = M.mergeWithKey (\_ a b -> Just $ comb a b) (M.map mk) (const M.empty)+superLeft :: (a -> b -> c) -> (a -> c) -> IntMap a -> IntMap b -> IntMap c+superLeft comb mk = IM.mergeWithKey (\_ a b -> Just $ comb a b) (IM.map mk) (const IM.empty)   -- | Choose between two alternatives with coin chance flipCoin :: MonadRandom m => a -> a -> m a-flipCoin a1 a2 = uniform [a1, a2]+flipCoin a1 a2 = do+  roll <- getRandom+  return $ if roll then a1 else a2   -- | Crossover on just the connections. Put the fittest map first.-crossConns :: MonadRandom m => Parameters -> Map InnoId ConnGene -> Map InnoId ConnGene ->-              m (Map InnoId ConnGene)+crossConns :: MonadRandom m => MutParams -> IntMap ConnGene -> IntMap ConnGene ->+              m (IntMap ConnGene) crossConns params m1 m2 = T.sequence $ superLeft flipConn return m1 m2   where flipConn c1 c2 = do           if connEnabled c1 && connEnabled c2@@ -327,14 +384,14 @@   -- | Crossover on just nodes-crossNodes :: MonadRandom m => Map NodeId NodeGene -> Map NodeId NodeGene ->-              m (Map NodeId NodeGene)-crossNodes m1 m2 = T.sequence $ superLeft flipCoin return m1 m2+crossNodes :: IntMap NodeGene -> IntMap NodeGene ->+              IntMap NodeGene+crossNodes m1 m2 = superLeft (\a _ -> a) id m1 m2   -- | Crossover. The first argument is the fittest genome.-crossover :: MonadRandom m => Parameters -> Genome -> Genome -> m Genome-crossover params g1 g2 = Genome `liftM` newNodes `ap` newConns `ap` return newNextNode+crossover :: MonadRandom m => MutParams -> Genome -> Genome -> m Genome+crossover params g1 g2 = Genome newNodes `liftM` newConns `ap` return newNextNode   where newNextNode = max (nextNode g1) (nextNode g2)         newConns = crossConns params (connGenes g1) (connGenes g2)         newNodes = crossNodes (nodeGenes g1) (nodeGenes g2)@@ -342,15 +399,15 @@  -- | Breed two genomes together breed :: (MonadRandom m, MonadFresh InnoId m) =>-         Parameters -> Map ConnSig InnoId -> Genome -> Genome ->+         MutParams -> Map ConnSig InnoId -> Genome -> Genome ->          m (Map ConnSig InnoId, Genome) breed params innos g1 g2 =   crossover params g1 g2 >>= mutate params innos   -- | Gets differences where they exist-differences :: Map InnoId ConnGene -> Map InnoId ConnGene -> Map InnoId Double-differences = M.mergeWithKey (\_ c1 c2 -> Just $ oneDiff c1 c2) (const M.empty) (const M.empty)+differences :: IntMap ConnGene -> IntMap ConnGene -> IntMap Double+differences = IM.mergeWithKey (\_ c1 c2 -> Just $ oneDiff c1 c2) (const IM.empty) (const IM.empty)   where oneDiff c1 c2 = abs $ connWeight c1 - connWeight c2  @@ -364,22 +421,23 @@                  weightDiffs = differences conns1 conns2 -        weightFactor = M.foldl (+) 0 weightDiffs / fromIntegral (M.size weightDiffs)+        weightFactor = IM.foldl (+) 0 weightDiffs / fromIntegral (IM.size weightDiffs) -        ids1 = M.keysSet conns1-        ids2 = M.keysSet conns2+        ids1 = IM.keysSet conns1 +        ids2 = IM.keysSet conns2+         -- | The lower of the top bounds of innovation numbers-        edge = min (S.findMax ids1) (S.findMax ids2)+        edge = min (IS.findMax ids1) (IS.findMax ids2)          -- | Excess and Disjoint-        exJoints = (ids1 `S.difference` ids2) `S.union` (ids2 `S.difference` ids1)+        exJoints = (ids1 `IS.difference` ids2) `IS.union` (ids2 `IS.difference` ids1) -        (excess, disjoint) = S.partition (<= edge) exJoints+        (excess, disjoint) = IS.partition (>= edge) exJoints -        exFactor = fromIntegral $ S.size excess+        exFactor = fromIntegral $ IS.size excess -        disFactor = fromIntegral $ S.size disjoint+        disFactor = fromIntegral $ IS.size disjoint   graphParams :: GraphvizParams NodeId NodeGene Double Rational Rational@@ -396,7 +454,7 @@          , clusterID = iderizer          , fmtCluster = clusterizer          , fmtNode = const []-         , fmtEdge = \(_,_,w) -> [ toLabel w ]+         , fmtEdge = \(_,_,w) -> [ toLabel $ (printf "%.2f" w :: String) ]          }   where categorizer (nId, ng) = C (yHint ng) (N (nId, yHint ng))         iderizer 0 = Str "Input Layer"@@ -425,7 +483,63 @@ -- else that there really is a problem. renderGenome :: Genome -> IO () renderGenome g = runGraphvizCanvas Dot graph Xlib-  where nodes = M.toList . nodeGenes $ g-        edges = mapMaybe mkEdge . M.elems . connGenes $ g+  where nodes = map (first NodeId) . IM.toList . nodeGenes $ g+        edges = mapMaybe mkEdge . IM.elems . connGenes $ g         mkEdge ConnGene{..} = if connEnabled then Just (connIn, connOut, connWeight) else Nothing         graph = graphElemsToDot graphParams nodes edges+++-- | A nicer way to display a 'Genome' than the Show instance.+printGenome :: Genome -> IO ()+printGenome g = putStrLn $ unlines stuff+  where unwrap (NodeId x) = x+        eText True = ""+        eText False = "(Disabled)"+        stuff = [header, nHeader] ++ nInfo ++ [cHeader] ++ cInfo+        header = "Genetic Info:"+        nHeader = "Nodes:"+        nInfo = map mkNInfo . IM.toList $ nodeGenes g+        mkNInfo (x, NodeGene t _) = show x ++ "(" ++ show t ++ ")"+        cHeader = "\n\nConnections:"+        cInfo = map mkCInfo . IM.toList $ connGenes g+        mkCInfo (i, ConnGene{..}) =+          "\nInnovation " ++ show i +++          "\nConnection from " ++ show (unwrap connIn) ++ " to " +++          show (unwrap connOut) ++ " " ++ eText connEnabled +++          " with weight " ++ show connWeight+++-- | Parameters for search. The type parameter determines the intermediate+-- type for determining if a solution is valid.+data GenScorer score =+  GS { gScorer         :: Genome -> score -- ^ Scoring function+     , fitnessFunction :: score -> Double -- ^ Convert the score to a fitness+     , winCriteria     :: score -> Bool   -- ^ Determines if a result is win+     } +++uniq :: Ord a => [a] -> Bool+uniq = go S.empty+  where go _   [] = True+        go set (x:xs) = not (S.member x set) && go (S.insert x set) xs+++-- | Validates a 'Genome', returning Nothing on success.+validateGenome :: Genome -> Maybe [String]+validateGenome Genome{..} = case errRes of+                             [] -> Nothing+                             xs -> Just xs+  where nodeOk = case IM.maxViewWithKey nodeGenes of+                  Nothing -> Nothing+                  Just ((nid,_), _)+                    | nid < getNodeId nextNode -> Nothing+                    | otherwise -> Just "NodeId too low"+        connOk (ConnSig (NodeId n1) (NodeId n2))+          | IM.member n1 nodeGenes && IM.member n2 nodeGenes = Nothing+          | otherwise = Just "Connection gene between nonexistent nodes"+        connsOk = join . listToMaybe $ map connOk sigList+        sigList = map toConnSig . IM.elems $ connGenes+        nonDup+          | uniq sigList = Nothing+          | otherwise = Just "Non unique connection signatures"+        errRes = catMaybes [nodeOk, connsOk, nonDup]
src/Neet/Network.hs view
@@ -42,18 +42,21 @@                     , stepNetwork                     , snapshot                       -- ** Output+                    , pushThrough                     , getOutput                     ) where -import Data.Map (Map) import Data.Set (Set) import qualified Data.Set as S -import qualified Data.Map as M-import Data.List (foldl')+import Data.List (sortBy, foldl') +import qualified Data.IntMap as IM+import Data.IntMap (IntMap)+ import Neet.Genome +import Data.Function  -- | Modified sigmoid function from the original NEAT paper modSig :: Double -> Double@@ -62,9 +65,10 @@  -- | A single neuron data Neuron =-  Neuron { activation  :: Double            -- ^ The current activation-         , connections :: Map NodeId Double -- ^ The inputs to this Neuron-         , yHint       :: Rational          -- ^ Visualization height+  Neuron { activation  :: Double        -- ^ The current activation+         , connections :: IntMap Double -- ^ The inputs to this Neuron+         , yHeight     :: Rational      -- ^ Visualization height+         , neurType    :: NodeType      -- ^ Type, used in pushThrough          }   deriving (Show)            @@ -73,7 +77,7 @@ data Network =   Network { netInputs   :: [NodeId] -- ^ Which nodes are inputs           , netOutputs  :: [NodeId] -- ^ Which nodes are outputs-          , netState    :: Map NodeId Neuron+          , netState    :: IntMap Neuron           , netDepth    :: Int      -- ^ Upper bound on depth           }    deriving (Show)@@ -81,24 +85,24 @@  -- | Takes the previous step's activations and current inputs and gives a -- function to update a neuron.-stepNeuron :: Map NodeId Double -> Neuron -> Neuron-stepNeuron acts (Neuron _ conns yh) = Neuron (modSig weightedSum) conns yh-  where oneFactor nId w = (acts M.! nId) * w-        weightedSum = M.foldlWithKey' (\acc k w -> acc + oneFactor k w) 0 conns+stepNeuron :: IntMap Double -> Neuron -> Neuron+stepNeuron acts (Neuron _ conns yh nt) = Neuron (modSig weightedSum) conns yh nt+  where oneFactor nId w = (acts IM.! nId) * w+        weightedSum = IM.foldlWithKey' (\acc k w -> acc + oneFactor k w) 0 conns   -- | Steps a network one step. Takes the network and the current input, minus -- the bias. stepNetwork :: Network -> [Double] -> Network stepNetwork net@Network{..} ins = net { netState = newNeurons }-  where pairs = zip netInputs (ins ++ [1])+  where pairs = zipWith (\x y -> (getNodeId x, y)) netInputs (ins ++ [1]) -        acts = M.map activation netState+        acts = IM.map activation netState          -- | The previous state, except updated to have new inputs-        modState = foldl' (flip $ uncurry M.insert) acts pairs+        modState = foldl' (flip $ uncurry IM.insert) acts pairs -        newNeurons = M.map (stepNeuron modState) netState+        newNeurons = IM.map (stepNeuron modState) netState   -- | Steps a network for at least its depth@@ -108,34 +112,57 @@         go n ds = stepNetwork (go (n - 1) ds) ds  +-- | Specialized  'snapshot' for nonrecurrent nets. Will break on recurrent+-- connections.+pushThrough :: Network -> [Double] -> [Double]+pushThrough net inputs = output+  where nodeOrder = sortBy (compare `on` (yHeight . snd)) $ IM.toList nodeMap++        nodeMap = netState net+        +        nonInputs = filter (\p -> neurType (snd p) /= Input) nodeOrder++        inPairs = zip (map getNodeId $ netInputs net) (inputs ++ [1])++        initState = foldl' (flip $ uncurry IM.insert) IM.empty inPairs++        addOne :: IntMap Double -> (Int, Neuron) -> IntMap Double+        addOne acc (nId, neur) =+          IM.insert nId (activation (stepNeuron acc neur)) acc++        final = foldl' addOne initState nonInputs++        output = map ((final IM.!) . getNodeId) (netOutputs net)++ mkPhenotype :: Genome -> Network-mkPhenotype Genome{..} = (M.foldl' addConn nodeHusk connGenes) { netInputs = ins-                                                               , netOutputs = outs-                                                               , netDepth = dep }-  where addNode n@(Network _ _ s _) nId (NodeGene _ yh) =-          n { netState = M.insert nId (Neuron 0 M.empty yh) s+mkPhenotype Genome{..} = (IM.foldl' addConn nodeHusk connGenes) { netInputs = map NodeId ins+                                                                , netOutputs = map NodeId outs+                                                                , netDepth = dep }+  where addNode n@(Network _ _ s _) nId (NodeGene nt yh) =+          n { netState = IM.insert nId (Neuron 0 IM.empty yh nt) s             } -        ins = M.keys . M.filter (\ng -> nodeType ng == Input) $ nodeGenes-        outs = M.keys . M.filter (\ng -> nodeType ng == Output) $ nodeGenes+        ins = IM.keys . IM.filter (\ng -> nodeType ng == Input) $ nodeGenes+        outs = IM.keys . IM.filter (\ng -> nodeType ng == Output) $ nodeGenes          -- | Network without connections added-        nodeHusk = M.foldlWithKey' addNode (Network [] [] M.empty 0) nodeGenes+        nodeHusk = IM.foldlWithKey' addNode (Network [] [] IM.empty 0) nodeGenes          depthSet :: Set Rational-        depthSet = M.foldl' (flip S.insert) S.empty $ M.map Neet.Genome.yHint nodeGenes+        depthSet = IM.foldl' (flip S.insert) S.empty $ IM.map Neet.Genome.yHint nodeGenes          dep = S.size depthSet -        addConn2Node nId w (Neuron a cs yh) = Neuron a (M.insert nId w cs) yh+        addConn2Node nId w (Neuron a cs yh nt) = Neuron a (IM.insert nId w cs) yh nt          addConn net@Network{ netState = s } ConnGene{..}           | not connEnabled = net           | otherwise =-              let newS = M.adjust (addConn2Node connIn connWeight) connOut s+              let newS = IM.adjust (addConn2Node (getNodeId connIn) connWeight) (getNodeId connOut) s               in net { netState = newS }   -- | Gets the output of the current state getOutput :: Network -> [Double]-getOutput Network{..} = map (activation . (netState M.!)) netOutputs+getOutput Network{..} = map (activation . (netState IM.!) . getNodeId) netOutputs
src/Neet/Parameters.hs view
@@ -27,28 +27,29 @@ Portability : portable -} -module Neet.Parameters (Parameters(..), DistParams(..), defParams, defDP, smallParams ) where+module Neet.Parameters ( Parameters(..)+                       , DistParams(..)+                       , MutParams(..)+                       , defParams+                       , defDistParams+                       , defMutParams+                       , defMutParamsS+                       ) where   -- | The genetic parameters data Parameters =-  Parameters { mutWeightRate  :: Double -- ^ How often weights are mutated-             , newWeightRate  :: Double -- ^ How often weights are replaced if mutated-             , pertAmount     :: Double -- ^ Max amount of perturbation-             , weightRange    :: Double -- ^ A new max is between negative this and positive this-             , addConnRate    :: Double -- ^ How often new connections are made-             , addNodeRate    :: Double -- ^ How often new nodes are added-             , largeSize      :: Int    -- ^ The minimum size for a species to be considered large-             , disableChance  :: Double -- ^ How likely that a disabled parent results-                                        -- in a disabled child-             , distParams     :: DistParams -- ^ Parameters for the distance function-             , dropTime       :: Maybe Int -- ^ Drop a species if it doesn't improve for this long,-                                           -- and it hasn't hosted the most successful genome.-             , noCrossover    :: Double -- ^ Percent of population that mutates without crossover+  Parameters { mutParams     :: MutParams+             , mutParamsS    :: MutParams  -- ^ Mutation parameters for small populations+             , largeSize     :: Int        -- ^ The minimum size for a species to be considered large+             , distParams    :: DistParams -- ^ Parameters for the distance function+             , dropTime      :: Maybe Int -- ^ Drop a species if it doesn't improve for this long,+                                          -- and it hasn't hosted the most successful genome.              }   deriving (Show)  +-- | Distance Parameters data DistParams =   DistParams { dp1 :: Double -- ^ Coefficient to the number of excess genes              , dp2 :: Double -- ^ Coefficient to the number of disjoint genes@@ -58,29 +59,53 @@   deriving (Show)  +-- | Mutation Parameters+data MutParams =+  MutParams { mutWeightRate  :: Double -- ^ How often weights are mutated+            , newWeightRate  :: Double -- ^ How often weights are replaced if mutated+            , pertAmount     :: Double -- ^ Max amount of perturbation+            , weightRange    :: Double -- ^ A new max is between negative this and positive this+            , addConnRate    :: Double -- ^ How often new connections are made+            , addNodeRate    :: Double -- ^ How often new nodes are added+            , recurrencies   :: Bool   -- ^ Whether to allow recurrent connections+            , noCrossover    :: Double -- ^ Percent of population that mutates without crossover+            , disableChance  :: Double -- ^ How likely that a disabled parent results+                                       -- in a disabled child+            }+  deriving (Show)+++-- | Mutation parameters for defParams+defMutParams :: MutParams+defMutParams =+  MutParams { mutWeightRate = 0.8+            , newWeightRate = 0.1+            , pertAmount = 2.5+            , weightRange = 2.5+            , addConnRate = 0.3+            , addNodeRate = 0.03+            , recurrencies = False+            , noCrossover = 0.25+            , disableChance = 0.75+            } ++ -- | The parameters used in the original NEAT paper, except the perturbation amount -- and threshold for size. defParams :: Parameters defParams =-  Parameters { mutWeightRate = 0.8-             , newWeightRate = 0.1-             , pertAmount = 0.1    -- This value I made up-             , weightRange = 10    -- This one too-             , addConnRate = 0.3-             , addNodeRate = 0.03+  Parameters { mutParams = defMutParams+             , mutParamsS = defMutParamsS              , largeSize = 20-             , disableChance = 0.75-             , distParams = defDP+             , distParams = defDistParams              , dropTime = Just 15-             , noCrossover = 0.25              }   --- | Parameters used in the paper for small populations-smallParams :: Parameters-smallParams = defParams { addConnRate = 0.05 }+defMutParamsS :: MutParams+defMutParamsS = defMutParams { addConnRate = 0.05 }   -- | Parameters used for distance in the paper-defDP :: DistParams-defDP = DistParams 1 1 0.4 3+defDistParams :: DistParams+defDistParams = DistParams 1 1 0.4 3
src/Neet/Population.hs view
@@ -35,6 +35,7 @@ {-# LANGUAGE BangPatterns #-} module Neet.Population (                          Population(..)+                       , SpecId(..)                          -- * PopM                        , PopM                        , PopContext@@ -46,6 +47,10 @@                        , trainOnce                        , trainN                        , trainUntil+                         -- * Statistics+                       , speciesCount+                         -- * Debugging+                       , validatePopulation                        ) where  import Neet.Species@@ -58,7 +63,7 @@ import Data.Map (Map) import qualified Data.Map as M -import Data.List (foldl', maximumBy)+import Data.List (foldl', maximumBy, sortBy)  import Data.Maybe @@ -69,7 +74,7 @@ import Control.Applicative import Control.Monad -+import Control.Parallel.Strategies  import Data.Function @@ -87,7 +92,7 @@              , popCont   :: !PopContext         -- ^ Tracking state and fresh values              , nextSpec  :: !SpecId             -- ^ The next species ID              , popParams  :: Parameters        -- ^ Parameters for large species-             , popParamsS :: Parameters        -- ^ Parameters for small species+             , popGen    :: Int                -- ^ Current generation              }   deriving (Show) @@ -134,7 +139,8 @@      , psInputs  :: Int        -- ^ Number of inputs      , psOutputs :: Int        -- ^ Number of outputs      , psParams  :: Parameters -- ^ Parameters for large species-     , psParamsS :: Parameters -- ^ Parameters for small species+     , sparse    :: Maybe Int  -- ^ If Just n, will be sparse with n connections.+                               -- Otherwise fully connected.      }    deriving (Show) @@ -199,16 +205,20 @@         newSpecies (SB sId _ (g:gs)) = Just $ (sId, newSpec g gs)  --- | Generates a fully connected starter population, given a seed.+-- | Generates a starter population newPop :: Int -> PopSettings -> Population newPop seed PS{..} = fst $ runPopM generate initCont-  where popSize = psSize+  where Parameters{..} = psParams+        popSize = psSize         popBScore = 0         popBSpec = SpecId 1         initCont = PC (InnoId $ psInputs * psOutputs + 2) (mkStdGen seed)         popParams = psParams-        popParamsS = psParamsS-        generateGens = replicateM psSize (fullConn psParams psInputs psOutputs)+        orgGenner = case sparse of+                     Nothing -> fullConn mutParams+                     Just conCount -> sparseConn mutParams conCount+        generateGens = replicateM psSize (orgGenner psInputs psOutputs)+        popGen = 1         generate = do           gens <- generateGens           let (popSpecs, nextSpec) = runSpecM (speciate psParams M.empty gens) (SpecId 1)@@ -217,29 +227,40 @@           return Population{..}  --- | Advances the population one generation with the fitness function.-trainOnce :: (Genome -> Double) -> Population -> Population-trainOnce f pop = generated+-- | Advances the population one generation with the fitness function, possibly+-- giving a solution.+trainOnce :: GenScorer a -> Population -> (Population, Maybe Genome)+trainOnce scorer pop = (generated, msolution)   where params = popParams pop-        paramsS = popParamsS pop -        chooseParams :: Species -> Parameters-        chooseParams s = if specSize s >= largeSize params then params else paramsS+        mParams = mutParams params+        mParamsS = mutParamsS params+          +        chooseParams :: Species -> MutParams+        chooseParams s = if specSize s >= largeSize params then mParams else mParamsS         {-# INLINE chooseParams #-}                  initSpecs = popSpecs pop +        oneEval :: Strategy (Species, TestResult)+        oneEval = evalTuple2 r0 rseq+         -- | Map to fitness data from runFitTest-        fits = M.map (\sp -> (sp, runFitTest f sp)) initSpecs+        fits = M.map (\sp -> (sp, runFitTest scorer sp)) initSpecs `using` parTraversable oneEval +        msolution = go $ map (trSol . snd) $ M.elems fits+          where go [] = Nothing+                go (Just x:_) = Just x+                go (_:xs) = go xs+         -- | Whether a species deserves to live (did it improve recently?)-        eugenics :: SpecId -> (Species, (MultiMap Double Genome, SpecScore, Double)) ->+        eugenics :: SpecId -> (Species, TestResult) ->                     Maybe (Species, MultiMap Double Genome, Double)-        eugenics sId (sp, (fitmap, ss, adj))+        eugenics sId (sp, TR{..})           | maybe False (lastImprovement nSpec >=) (dropTime params)             && sId /= popBSpec pop = Nothing-          | otherwise = Just (nSpec, fitmap, adj)-          where nSpec = updateSpec ss sp+          | otherwise = Just (nSpec, trScores, trAdj)+          where nSpec = updateSpec trSS sp          -- | Species that have improved recently enough.         masterRace :: Map SpecId (Species, MultiMap Double Genome, Double)@@ -269,25 +290,29 @@         dubSize = fromIntegral totalSize          -- | Distribution of species.-        candSpecs :: MonadRandom m => [(Parameters, Int, m Genome)]+        candSpecs :: MonadRandom m => [(MutParams, Int, m (Double,Genome))]         candSpecs = zip3 ps realShares pickers-          where initShares = map share masterList-                share (_,(_, _, adj)) = round $ adj / totalFitness * dubSize+          where sortedMaster = sortBy revComp masterList+                -- | Reversed comparison on best score, to get a descending sorted list+                revComp (_,(sp1,_,_)) (_,(sp2,_,_)) = (compare `on` (bestScore . specScore)) sp2 sp1+                initShares = map share sortedMaster+                share (_,(_, _, adj)) = floor $ adj / totalFitness * dubSize                 remaining = totalSize - foldl' (+) 0 initShares                 distributeRem _ [] = error "Should run out of numbers first"                 distributeRem n l@(x:xs)                   | n > 0 = x + 1 : distributeRem (n - 1) xs+                  | n < 0 = error "Remainder should be positive"                   | otherwise = l                 realShares = distributeRem remaining initShares-                pickers :: MonadRandom m => [m Genome]-                pickers = map picker masterList+                pickers :: MonadRandom m => [m (Double, Genome)]+                pickers = map picker sortedMaster                   where picker (_,(s, mmap, _)) =                           let numToTake = specSize s `div` 5 + 1                               desc = M.toDescList $ MM.toMap mmap                               toPairs (k, vs) = map (\v -> (k,v)) vs                               culled = take numToTake $ desc >>= toPairs-                          in fromList . map (\(d,g) -> (g, toRational d)) $ culled-                ps = map (\(_,(s,_,_)) -> chooseParams s) masterList+                          in uniform culled+                ps = map (\(_,(s,_,_)) -> chooseParams s) sortedMaster          applyN :: Monad m => Int -> (a -> m a) -> a -> m a         applyN 0 _  x = return x@@ -295,23 +320,30 @@          -- | Generate the genomes for a species         specGens :: (MonadFresh InnoId m, MonadRandom m) =>-                    (Parameters, Int, m Genome) -> m [Genome]-        specGens (p, n, gen) = liftM snd $ applyN n genOne (M.empty, [])+                    Map ConnSig InnoId -> (MutParams, Int, m (Double, Genome)) ->+                    m (Map ConnSig InnoId, [Genome])+        specGens inns (p, n, gen) = applyN n genOne (inns, [])           where genOne (innos, gs) = do                   roll <- getRandomR (0,1)                   if roll <= noCrossover p                     then do-                    parent <- gen+                    (_,parent) <- gen                     (innos', g) <- mutate p innos parent                     return (innos', g:gs)                     else do-                    mom <- gen-                    dad <- gen-                    (innos', g) <- breed p innos mom dad+                    (fit1, mom) <- gen+                    (fit2, dad) <- gen+                    (innos', g) <- if fit1 > fit2+                                   then breed p innos mom dad+                                   else breed p innos dad mom                     return (innos', g:gs)          allGens :: (MonadRandom m, MonadFresh InnoId m) => m [Genome]-        allGens = liftM concat $ mapM specGens candSpecs+        allGens = liftM (concat . snd) $ foldM ag' (M.empty, []) candSpecs+          where ag' (innos, cands) cand = do+                  (innos', specGen) <- specGens innos cand+                  return $ (innos', specGen:cands)+                            genNewSpecies :: (MonadRandom m, MonadFresh InnoId m) => m (Map SpecId Species, SpecId)         genNewSpecies = do@@ -338,26 +370,48 @@                      , popBSpec = bSpec                      , popCont = cont'                      , nextSpec = nextSpec'+                     , popGen = popGen pop + 1                      }    -- | Train the population n times. Values less than 1 return the original.-trainN :: Int -> (Genome -> Double) -> Population -> Population-trainN n f p+trainN :: Int -> GenScorer a -> Population -> Population+trainN n scorer p   | n <= 0 = p-  | otherwise = applyN n (trainOnce f) p+  | otherwise = applyN n (trainOnce scorer) p   where applyN 0  _ !x = x-        applyN n' h !x = applyN (n' - 1) h (h x)+        applyN n' h !x = applyN (n' - 1) h (fst $ h x)  --- | Train until the given fitness (first parameter) is reached, or the max number--- of generations (second parameter) is reached. Also gives generations needed.-trainUntil :: Double -> Int -> (Genome -> Double) -> Population -> (Population, Int)-trainUntil goal n f p-  | n <= 0 = (p, 0)+-- | Train until the provided goal is reached, or the max number+-- of generations (first parameter) is reached. Possibly also returns a solution+-- and the number of generations elapsed.+trainUntil :: Int -> GenScorer a -> Population -> (Population, Maybe (Genome, Int))+trainUntil n f p+  | n <= 0 = (p, Nothing)   | otherwise = go n p-  where go 0  !p' = (p', n)-        go n' !p'-          | reached = (p', n - n')-          | otherwise = go (n' - 1) (trainOnce f p')-          where reached = popBScore p' >= goal+  where go 0  !p' = (p', Nothing)+        go n' !p' = case trainOnce f p' of+                     (p'', Nothing) -> go (n' - 1) p''+                     (p'', Just g) -> (p'', Just (g, n - n'))+++-- | Gets the number of species+speciesCount :: Population -> Int+speciesCount Population{..} = M.size popSpecs+++-- | Validate a population, possibly returning a list of errors+validatePopulation :: Population -> Maybe [String]+validatePopulation Population{..} = case errRes of+                                     [] -> Nothing+                                     xs -> Just xs+  where totalSSize = M.foldl' (\acc x -> specSize x + acc) 0 popSpecs+        goodSize+          | totalSSize == popSize = []+          | otherwise = ["Population size differs from actual size"]+        goodSId+          | (not . M.null) popSpecs && fst (M.findMax popSpecs) < nextSpec = []+          | otherwise = ["SpecId lower than extant species"]+        specErrs = concat . M.elems $ M.mapMaybe validateSpecies popSpecs+        errRes = goodSId  ++ goodSize ++ specErrs
src/Neet/Species.hs view
@@ -28,22 +28,34 @@ -}  {-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE BangPatterns #-} module Neet.Species (                       Species(..)                     , SpecScore(..)                       -- * Construction                     , newSpec                       -- * Update/Fitness+                    , TestResult(..)                     , runFitTest                     , updateSpec+                      -- * Statistics+                    , maxDist+                      -- * Debugging+                    , validateSpecies                     ) where   import Neet.Genome+import Neet.Parameters+ import Data.MultiMap (MultiMap) import qualified Data.MultiMap as MM import Data.List (foldl')+import Data.Maybe +import Control.Applicative ((<$>), (<*>))++ -- | A NEAT Species. data Species =   Species { specSize :: Int@@ -54,7 +66,7 @@   -- | Scoring data-data SpecScore = SpecScore { bestScore :: Double, bestGen :: Genome }+data SpecScore = SpecScore { bestScore :: !Double, bestGen :: !Genome }   instance Show Species where@@ -70,13 +82,31 @@ newSpec gen gens = Species (length gens + 1) (gen:gens) (SpecScore 0 gen) 0  +-- | A result of evaluating a species+data TestResult =+  TR { trScores :: MultiMap Double Genome -- ^ The score of each organism+     , trSS     :: !SpecScore              -- ^ Result 'SpecScore'+     , trAdj    :: !Double                 -- ^ Total adjusted fitness+     , trSol    :: !(Maybe Genome)           -- ^ Possible Solution+     }++findMay :: (a -> Bool) -> [a] -> Maybe a+findMay _ [] = Nothing+findMay p (a:as)+  | p a = Just a+  | otherwise = findMay p as++ -- | Output the result of testing fitness. Last value is the total adjusted fitness-runFitTest :: (Genome -> Double) -> Species -> (MultiMap Double Genome, SpecScore, Double)-runFitTest f Species{..} = (mmap, ss, totF / dubSize)+runFitTest :: GenScorer a -> Species -> TestResult+runFitTest GS{..} Species{..} = TR mmap ss (totF / dubSize) msolution   where dubSize = fromIntegral specSize :: Double-        (mmap, totF) = foldl' accumOne (MM.empty, 0) $ map calcOne specOrgs-        calcOne g = let fitness = f g in (fitness, g)-        accumOne (accM, accA) (fit, g) = (MM.insert fit g accM, accA + fit)+        (mmap, totF) = foldl' accumOne (MM.empty, 0) resses+        calcOne g = let !score = gScorer g in (score, g)+        resses = map calcOne specOrgs+        msolution = fmap snd . findMay (\pair -> winCriteria (fst pair)) $ resses+        accumOne (accM, accA) (score, g) = (MM.insert fit g accM, accA + fit)+          where fit = fitnessFunction score         ss = case MM.findMaxWithValues mmap of               Nothing -> error "(runFitTest) folding fitness resulted in empty map!"               Just (scr, (x:_)) -> SpecScore scr x@@ -92,3 +122,19 @@         (newScr, li)           | bestScore ss > bestScore oldScr = (ss, 0)           | otherwise                       = (oldScr, lastImprovement spec + 1)+++-- | Validates a species, possibly returning errors+validateSpecies :: Species -> Maybe [String]+validateSpecies Species{..} = case orgErrs ++ goodSize of+                               [] -> Nothing+                               xs -> Just xs+  where orgErrs = concat $ mapMaybe validateGenome specOrgs+        goodSize+          | specSize == length specOrgs = []+          | otherwise = ["Species size differs from number of organisms"]+        ++-- | Gets the max distance between two genomes in a species+maxDist :: Parameters -> Species -> Double+maxDist ps Species{..} = maximum . map (uncurry (distance ps)) $ (,) <$> specOrgs <*> specOrgs