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]})))