From c40f1f70f445b162e987fe4e0395bcef9d23be25 Mon Sep 17 00:00:00 2001 From: Ryan Boldi Date: Thu, 5 Jan 2023 12:40:15 +0100 Subject: [PATCH] added fitness proportionate selection + tests --- src/propeller/selection.cljc | 20 +++++++++++++++++++- test/propeller/selection_test.cljc | 23 +++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 test/propeller/selection_test.cljc diff --git a/src/propeller/selection.cljc b/src/propeller/selection.cljc index 487ecf2..b89ab51 100755 --- a/src/propeller/selection.cljc +++ b/src/propeller/selection.cljc @@ -21,9 +21,27 @@ survivors) (rest cases)))))) +(defn fitness-proportionate-selection + "Selects an individual from the population using a fitness proportionate selection." + [pop argmap] + (let [pop-fits (->> pop ;convert from error to fitness, where fitness (probability) is (1/ (1+ tot_err)) + (map #(assoc % :fitness (/ 1 (inc (:total-error %)))))) + pop-total-fit (->> pop-fits + (map :fitness) + (reduce +)) + random-num (* (rand) pop-total-fit) + sorted-by-fitness (->> pop-fits + (sort-by :fitness) + (reverse))] + (loop [tot (:fitness (first sorted-by-fitness)) individuals sorted-by-fitness] + (if (< random-num tot) + (first individuals) + (recur (+ tot (:fitness (first (rest individuals)))) (rest individuals)))))) + (defn select-parent "Selects a parent from the population using the specified method." [pop argmap] (case (:parent-selection argmap) :tournament (tournament-selection pop argmap) - :lexicase (lexicase-selection pop argmap))) + :lexicase (lexicase-selection pop argmap) + :roulette (fitness-proportionate-selection pop argmap))) \ No newline at end of file diff --git a/test/propeller/selection_test.cljc b/test/propeller/selection_test.cljc new file mode 100644 index 0000000..669d873 --- /dev/null +++ b/test/propeller/selection_test.cljc @@ -0,0 +1,23 @@ +(ns propeller.selection-test + (:require [clojure.test :as t] + [propeller.selection :as s])) + + +(t/deftest roulette-selection-test + (t/testing "fitness proportionate selection" + (t/testing "should correctly define the probabilities of selection" + (t/is (let [ret (s/fitness-proportionate-selection '({:index 0 :total-error 0} + {:index 1 :total-error 1} + {:index 2 :total-error 1} + {:index 3 :total-error 1}) {:empty :argmap})] + (case (:index ret) + 0 (= (:fitness ret) 1) ;if we selected index 0, check that fitness is correctly calculated to 1 + (= (:fitness ret) 1/2) + )))) + (t/testing "should always return the same individual if there is only one" + (t/testing "desipte it having bad error" + (t/is (= (:index (s/fitness-proportionate-selection '({:index 99 :total-error 109012390123}) {:empty :argmap})) + 99))) + (t/testing "when it has low error" + (t/is (= (:index (s/fitness-proportionate-selection '({:index 22 :total-error 0}) {:empty :argmap})) + 22)))))) \ No newline at end of file