001 (ns nature.core
002 (:require [nature.spec :as s]
003 [nature.population-presets :as pp]
004 [nature.initialization-operators :as io]
005 [nature.genetic-operators :as go]
006 [nature.population-operators :as po]
007 [nature.monitors :as monitors])
008 (:gen-class))
009
010 (defn evolve
011 "Create and evolve a population under the specified conditions until a termination criteria is reached
012 `allele-set` is a collection of legal genome values
013 `genome-length` is the enforced size of each genetic sequence
014 `population-size` is the enforced number of individuals that will be created
015 `generations` is the number of iterations the algorithm will cycle through
016 `fitness-function` is a partial function accepting generated sequences to evaluate solution qualities
017 `binary-operators` is a collection of partial functions accepting and returning 1 or more individuals
018 `unary-operators` is a collection of partial functions accepting and returning exactly 1 individual
019 `options` an optional map of pre-specified keywords to values that further tune the behavior of nature.
020 Current examples follow:
021 `:carry-over` an integer representing the top n individuals to be carried over between each generation. Default is 1
022 `:solutions` an integer representing the top n individuals to return after evolution completes. Default is 1
023 `:monitors` a sequence of functions, assumed to be side-effectful, to be executed against `population` and `current-genration` for run-time stats. Default is nil"
024 ([allele-set genome-length population-size generations fitness-function binary-operators unary-operators]
025 (evolve allele-set genome-length population-size generations fitness-function binary-operators unary-operators {:solutions 1, :carry-over 1}))
026
027 ([allele-set genome-length population-size generations fitness-function binary-operators unary-operators options] ;; TODO - Curry the genetic operators one more level, so the fitness-function can be pressed in
028 {:pre [(and (every? coll? [allele-set binary-operators unary-operators])
029 (every? int? [genome-length population-size generations])
030 (fn? fitness-function))]}
031 (let [solutions (max 1 (:solutions options))
032 carry-over (max 1 (:carry-over options))
033 monitors (:monitors options)]
034 (loop [population (io/build-population population-size allele-set genome-length fitness-function)
035 current-generation 0]
036 (when monitors (monitors/apply-monitors monitors population current-generation))
037 (if (>= current-generation generations)
038 (take solutions (sort-by :fitness-score #(> %1 %2) population))
039 (recur (po/advance-generation population population-size binary-operators unary-operators {:carry-over carry-over}) (inc current-generation)))))))
040
041 (defn -main
042 "A very, very simple example"
043 [& args]
044 (println (evolve pp/binary-genome
045 pp/default-sequence-length
046 pp/default-population-size
047 pp/default-generation-count
048 pp/sum-alleles
049 [(go/crossover pp/sum-alleles)]
050 [(partial go/mutation-operator pp/sum-alleles pp/binary-genome 1)]
051 {:solutions 1, :carry-over 5, :monitors [monitors/print-best-solution]})))