From fac7b03b0c9ac3c1be7630beaade6ee58fb7a1d0 Mon Sep 17 00:00:00 2001 From: Lee Spector Date: Tue, 7 Nov 2023 20:05:01 -0500 Subject: [PATCH 1/5] Fix definition of E --- src/propeller/tools/math.cljc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/propeller/tools/math.cljc b/src/propeller/tools/math.cljc index 6102316..d3be7ca 100644 --- a/src/propeller/tools/math.cljc +++ b/src/propeller/tools/math.cljc @@ -5,7 +5,7 @@ :cljs js/Math.PI)) (defonce ^{:no-doc true :const true} E #?(:clj Math/E - :cljs js/Math.PI)) + :cljs js/Math.E)) (defn step "returns 1 if number is nonzero, 0 otherwise" @@ -141,4 +141,4 @@ (defn transpose "returns a vector containing the transpose of a coll of colls" [x] - (apply map vector x)) \ No newline at end of file + (apply map vector x)) From 1a1f44285815fa11b4fd993f81b4e02ad20ca109 Mon Sep 17 00:00:00 2001 From: Tom Helmuth Date: Tue, 7 Nov 2023 20:23:49 -0500 Subject: [PATCH 2/5] Tom's opinionated better logging --- src/propeller/gp.cljc | 10 +++++----- src/propeller/utils.cljc | 11 +++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/propeller/gp.cljc b/src/propeller/gp.cljc index 852b26a..9b1d3a5 100644 --- a/src/propeller/gp.cljc +++ b/src/propeller/gp.cljc @@ -1,7 +1,6 @@ (ns propeller.gp "Main genetic programming loop." (:require [clojure.string] - [clojure.pprint] [propeller.genome :as genome] [propeller.simplification :as simplification] [propeller.variation :as variation] @@ -22,20 +21,21 @@ "Reports information each generation." [evaluations pop generation argmap training-data] (let [best (first pop)] - (clojure.pprint/pprint + (utils/pretty-map-println {:generation generation :best-plushy (:plushy best) :best-program (genome/plushy->push (:plushy best) argmap) :best-total-error (:total-error best) :evaluations evaluations - :ds-indices (map #(:index %) training-data) + :ds-indices (if (:downsample? argmap) + (map #(:index %) training-data) + nil) :best-errors (:errors best) :best-behaviors (:behaviors best) :genotypic-diversity (float (/ (count (distinct (map :plushy pop))) (count pop))) :behavioral-diversity (float (/ (count (distinct (map :behaviors pop))) (count pop))) :average-genome-length (float (/ (reduce + (map count (map :plushy pop))) (count pop))) - :average-total-error (float (/ (reduce + (map :total-error pop)) (count pop)))}) - (println))) + :average-total-error (float (/ (reduce + (map :total-error pop)) (count pop)))}))) (defn cleanup [] diff --git a/src/propeller/utils.cljc b/src/propeller/utils.cljc index ea03d29..afc2faf 100755 --- a/src/propeller/utils.cljc +++ b/src/propeller/utils.cljc @@ -151,3 +151,14 @@ (if (coll? thing-or-collection) (rand-nth thing-or-collection) thing-or-collection)) + +(defn pretty-map-println + "Takes a map and prints it, with each key/value pair on its own line." + [mp] + (print "{") + (let [mp-seq (seq mp) + [first-key first-val] (first mp-seq)] + (println (pr-str first-key first-val)) + (doseq [[k v] (rest mp-seq)] + (println (str " " (pr-str k v))))) + (println "}")) From e00e34402cc78feba66a5d7f7598f2001aa4d84e Mon Sep 17 00:00:00 2001 From: Tom Helmuth Date: Tue, 7 Nov 2023 20:43:24 -0500 Subject: [PATCH 3/5] Added float_div instruction --- src/propeller/push/instructions/numeric.cljc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/propeller/push/instructions/numeric.cljc b/src/propeller/push/instructions/numeric.cljc index ff47336..7de1e3c 100755 --- a/src/propeller/push/instructions/numeric.cljc +++ b/src/propeller/push/instructions/numeric.cljc @@ -183,6 +183,14 @@ Otherwise, acts as a NOOP" ;; FLOAT Instructions only ;; ============================================================================= +;; Divides the top two items on the float stack +;; If denominator is 0, returns 1.0 +(def-instruction + :float_div + ^{:stacks #{:float}} + (fn [state] + (make-instruction state #(float (if (zero? %2) 1 (/ %1 %2))) [:float :float] :float))) + ;; Pushes the cosine of the top FLOAT (def-instruction :float_cos From a7ce4152a2541f1f40130b5c2eeb59aa87d04160 Mon Sep 17 00:00:00 2001 From: Tom Helmuth Date: Tue, 7 Nov 2023 20:52:52 -0500 Subject: [PATCH 4/5] Updated readme --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 59a7fc1..c7e68e0 100644 --- a/README.md +++ b/README.md @@ -125,8 +125,9 @@ Calling `(-main)` will run the default genetic programming problem. ## Description -Propel is an implementation of the Push programming -language and the PushGP genetic programming system in Clojure. +Propeller is an implementation of the Push programming +language and the PushGP genetic programming system in Clojure, based +on Tom Helmuth's little PushGP implementation [propel](https://github.com/thelmuth/propel). For more information on Push and PushGP see [http://pushlanguage.org](http://pushlanguage.org). From 7ca3767b0267d9d529596558e0dc094b4cb64a58 Mon Sep 17 00:00:00 2001 From: Ryan Boldi Date: Tue, 7 Nov 2023 22:33:58 -0500 Subject: [PATCH 5/5] remove hacky hyperselection code --- src/propeller/gp.cljc | 15 ++++------ src/propeller/hyperselection.cljc | 36 ------------------------ src/propeller/variation.cljc | 8 ++---- test/propeller/push/downsample_test.cljc | 28 ++---------------- test/propeller/utils_test.cljc | 3 +- 5 files changed, 12 insertions(+), 78 deletions(-) delete mode 100644 src/propeller/hyperselection.cljc diff --git a/src/propeller/gp.cljc b/src/propeller/gp.cljc index 9b1d3a5..7c74af6 100644 --- a/src/propeller/gp.cljc +++ b/src/propeller/gp.cljc @@ -5,7 +5,6 @@ [propeller.simplification :as simplification] [propeller.variation :as variation] [propeller.downsample :as downsample] - [propeller.hyperselection :as hyperselection] [propeller.push.instructions.bool] [propeller.push.instructions.character] [propeller.push.instructions.code] @@ -125,17 +124,15 @@ :else (recur (inc generation) (+ evaluations (* population-size (count training-data)) ;every member evaluated on the current sample (if (zero? (mod generation ds-parent-gens)) (* (count parent-reps) (- (count indexed-training-data) (count training-data))) 0) ; the parent-reps not evaluted already on down-sample - (if best-individual-passes-ds (- (count indexed-training-data) (count training-data)) 0)) ; if we checked for generalization or not - (let [reindexed-pop (hyperselection/reindex-pop evaluated-pop argmap)] ; give every individual an index for hyperselection loggin - (hyperselection/log-hyperselection-and-ret - (if (:elitism argmap) - (conj (utils/pmapallv (fn [_] (variation/new-individual reindexed-pop argmap)) + (if best-individual-passes-ds (- (count indexed-training-data) (count training-data)) 0)) ; if we checked for generalization or not + (if (:elitism argmap) + (conj (utils/pmapallv (fn [_] (variation/new-individual evaluated-pop argmap)) (range (dec population-size)) argmap) - (first reindexed-pop)) ;elitism maintains the most-fit individual - (utils/pmapallv (fn [_] (variation/new-individual reindexed-pop argmap)) + (first evaluated-pop)) ;elitism maintains the most-fit individual + (utils/pmapallv (fn [_] (variation/new-individual evaluated-pop argmap)) (range population-size) - argmap)))) + argmap)) (if downsample? (if (zero? (mod generation ds-parent-gens)) (downsample/update-case-distances rep-evaluated-pop indexed-training-data indexed-training-data ids-type (/ solution-error-threshold (count indexed-training-data))) ; update distances every ds-parent-gens generations diff --git a/src/propeller/hyperselection.cljc b/src/propeller/hyperselection.cljc deleted file mode 100644 index 116f761..0000000 --- a/src/propeller/hyperselection.cljc +++ /dev/null @@ -1,36 +0,0 @@ -(ns propeller.hyperselection - (:require [propeller.utils :as utils])) - -(defn sum-list-map-indices - "sums a list of maps that have the :index property's index multiplicity" - [list-of-maps] - (->> list-of-maps - (map #(:index %)) - frequencies)) - -(defn ordered-freqs - "takes a map from indices to frequencies, and returns a sorted list of the frequences is descencing order" - [freqs] - (->> freqs - vals - (sort >))) - -(defn normalize-list-by-popsize [popsize lst] - (map #(double (/ % popsize)) lst)) - -(defn hyperselection-track - "outputs a normalized list of the hyperselection proportion for each parent" - [new-pop] - (->> new-pop - sum-list-map-indices - ordered-freqs - (normalize-list-by-popsize (count new-pop)))) - -(defn log-hyperselection-and-ret [new-pop] - (prn {:hyperselection (hyperselection-track new-pop)}) - new-pop) - -(defn reindex-pop - "assigns each member of the population a unique index before selection to track hyperselection" - [pop argmap] - (utils/pmapallv (fn [indiv index] (assoc indiv :index index)) pop (range (count pop)) argmap)) \ No newline at end of file diff --git a/src/propeller/variation.cljc b/src/propeller/variation.cljc index e4825ab..8af9fd3 100644 --- a/src/propeller/variation.cljc +++ b/src/propeller/variation.cljc @@ -252,10 +252,8 @@ The function `new-individual` returns a new individual produced by selection and (defn new-individual "Returns a new individual produced by selection and variation of individuals in the population." - [pop argmap] - (let [umad-parent (selection/select-parent pop argmap) - parent-ind (:index umad-parent)] ;this is a hack to log hyperselection, only works for umad - {:plushy + [pop argmap] + {:plushy (let [r (rand) op (loop [accum 0.0 ops-probs (vec (:variation argmap))] @@ -347,4 +345,4 @@ The function `new-individual` returns a new individual produced by selection and :else (throw #?(:clj (Exception. (str "No match in new-individual for " op)) :cljs (js/Error - (str "No match in new-individual for " op))))))})) + (str "No match in new-individual for " op))))))}) diff --git a/test/propeller/push/downsample_test.cljc b/test/propeller/push/downsample_test.cljc index 517f421..0e6113f 100644 --- a/test/propeller/push/downsample_test.cljc +++ b/test/propeller/push/downsample_test.cljc @@ -2,8 +2,7 @@ (:require [clojure.test :as t] [propeller.utils :as u] [propeller.simplification :as s] - [propeller.downsample :as ds] - [propeller.hyperselection :as hs])) + [propeller.downsample :as ds])) (t/deftest assign-indices-to-data-test @@ -137,27 +136,4 @@ {:input1 [4] :output1 [14] :index 4 :distances [0 0 0 0 0]}) {:case-delta 0})] (prn {:selected selected}) - (t/is (= 1 (count selected)))))) - - -(t/deftest hyperselection-test - (let [parents1 '({:blah 3 :index 1} {:blah 3 :index 1} - {:blah 3 :index 1} {:blah 3 :index 2}) - parents2 '({:plushy 2 :index 0} {:blah 3 :index 2} - {:blah 3 :index 3} {:index 4}) - emptyparents '({:blah 1} {:blah 1} {:blah 1})] - (t/testing "sum-list-map-indices function works correctly" - (t/is (= {1 3, 2 1} (hs/sum-list-map-indices parents1))) - (t/is (= {0 1, 2 1, 3 1, 4 1} (hs/sum-list-map-indices parents2)))) - (t/testing "ordered-freqs function works correctly" - (t/is (= '(3 1) (hs/ordered-freqs (hs/sum-list-map-indices parents1)))) - (t/is (= '(1 1 1 1) (hs/ordered-freqs (hs/sum-list-map-indices parents2))))) - (t/testing "hyperselection-track works correctly" - (t/is (= '(0.75 0.25) (hs/hyperselection-track parents1))) - (t/is (= '(0.25 0.25 0.25 0.25) (hs/hyperselection-track parents2)))) - (t/testing "reindex-pop works correctly" - (t/is (= '({:blah 3 :index 0} {:blah 3 :index 1} - {:blah 3 :index 2} {:blah 3 :index 3}) (hs/reindex-pop parents1 {}))) - (t/is (= '({:plushy 2 :index 0} {:blah 3 :index 1} - {:blah 3 :index 2} {:index 3}) (hs/reindex-pop parents2 {}))) - (t/is (= '({:blah 1 :index 0} {:blah 1 :index 1} {:blah 1 :index 2}) (hs/reindex-pop emptyparents {})))))) + (t/is (= 1 (count selected)))))) \ No newline at end of file diff --git a/test/propeller/utils_test.cljc b/test/propeller/utils_test.cljc index cc36fec..1461134 100644 --- a/test/propeller/utils_test.cljc +++ b/test/propeller/utils_test.cljc @@ -2,8 +2,7 @@ (:require [clojure.test :as t] [propeller.utils :as u] [propeller.simplification :as s] - [propeller.downsample :as ds] - [propeller.hyperselection :as hs])) + [propeller.downsample :as ds])) (t/deftest first-non-nil-test (t/is (= 1 (u/first-non-nil '(1 2 3))))