From 648c7b866a789fdbdad5b1e900bc63c1218edccf Mon Sep 17 00:00:00 2001
From: Lee Spector <lspector@hampshire.edu>
Date: Thu, 7 Dec 2023 11:28:21 -0500
Subject: [PATCH] Implement :gap mutation in :bmx-umad according to
 :bmx-gap-change-probability

---
 src/propeller/problems/boolean/mul3.cljc | 40 +++++++++++++-----------
 src/propeller/variation.cljc             | 35 +++++++++++++++------
 2 files changed, 48 insertions(+), 27 deletions(-)

diff --git a/src/propeller/problems/boolean/mul3.cljc b/src/propeller/problems/boolean/mul3.cljc
index b1f30df..c46d9f4 100644
--- a/src/propeller/problems/boolean/mul3.cljc
+++ b/src/propeller/problems/boolean/mul3.cljc
@@ -294,22 +294,26 @@
   [& args]
   (gp/gp
    (merge
-    {:instructions             instructions
-     :error-function           error-function
-     :training-data            (:train train-and-test-data)
-     :testing-data             (:test train-and-test-data)
-     :max-generations          1000
-     :population-size          1000
-     :max-initial-plushy-size  100
-     :step-limit               10000
-     :parent-selection         :lexicase
-     :downsample?              false
-     :ds-function              :case-rand
-     :downsample-rate          0.1
-     :umad-rate                0.01
-     :variation                {:umad 0
-                                :bmx 0
-                                :bmx-umad 1}
-     :single-thread-mode       false
-     :bmx-exchange-rate        0.1}
+    {:instructions               instructions
+     :error-function             error-function
+     :training-data              (:train train-and-test-data)
+     :testing-data               (:test train-and-test-data)
+     :max-generations            1000
+     :population-size            1000
+     :max-initial-plushy-size    100
+     :step-limit                 10000
+     :parent-selection           :lexicase
+     :downsample?                false
+     :ds-function                :case-rand
+     :downsample-rate            0.1
+     :umad-rate                  0.01
+     :variation                  {:umad 0
+                                  :bmx 0
+                                  :bmx-umad 1}
+     :single-thread-mode         false
+     :bmx?                       true
+     :bmx-exchange-rate          0.5
+     :bmx-gap-probability        0.1
+     :bmx-gap-change-probability 0.01
+     :bmx-complementary?         true}
     (apply hash-map (map #(if (string? %) (read-string %) %) args)))))
diff --git a/src/propeller/variation.cljc b/src/propeller/variation.cljc
index a1f1cd1..a7c804e 100644
--- a/src/propeller/variation.cljc
+++ b/src/propeller/variation.cljc
@@ -130,11 +130,28 @@ The function `new-individual` returns a new individual produced by selection and
   [plushy umad-rate]
   (if (zero? umad-rate)
     plushy
-    (remove (fn [item]
-              (and (not= item :gap)
-                   (< (rand)
-                      (/ 1 (+ 1 (/ 1 umad-rate))))))
-            plushy)))
+    (let [adjusted-rate (/ 1 (+ 1 (/ 1 umad-rate)))]
+      (remove (fn [item]
+                (and (not= item :gap)
+                     (< (rand) adjusted-rate)))
+              plushy))))
+
+(defn uniform-gap-deletion
+  "Randomly deletes instances of :gap from plushy at some rate, which has to be adjusted not
+   only because additions may already have happened, but also because while :gap can be added
+   anywhere, it can only be deleted where it already is."
+  [plushy gap-change-rate]
+  (let [gap-count (count (filter #(= % :gap) plushy))]
+    (if (or (zero? gap-change-rate)
+            (empty? plushy)
+            (zero? gap-count))
+      plushy
+      (let [adjusted-rate (/ 1 (/ (+ 1 (/ 1 gap-change-rate))
+                                  (/ (count plushy) gap-count)))]
+        (remove (fn [item]
+                  (and (= item :gap)
+                       (< (rand) adjusted-rate)))
+                plushy)))))
 
 (defn bmx
   "Crosses over two plushies using best match crossover (bmx)."
@@ -185,8 +202,8 @@ The function `new-individual` returns a new individual produced by selection and
                          (selection/select-parent pop argmap))
                plushy1 (:plushy parent1)
                plushy2 (:plushy parent2)
-               rate (utils/onenum (or (:bmx-exchange-rate argmap) 0.5))]
-           (bmx plushy1 plushy2 rate))
+               bmx-exchange-rate (utils/onenum (or (:bmx-exchange-rate argmap) 0.5))]
+           (bmx plushy1 plushy2 bmx-exchange-rate))
        ;
          :umad ;; uniform mutation by addition and deletion, see uniform-deletion for the
                ;; adjustment that makes this size neutral on average
@@ -209,8 +226,8 @@ The function `new-individual` returns a new individual produced by selection and
                       plushy2 (:plushy parent2)
                       bmx-exchange-rate (utils/onenum (or (:bmx-exchange-rate argmap) 0.5))]
                   (bmx plushy1 plushy2 bmx-exchange-rate))
-                (uniform-addition (:instructions argmap) umad-rate)
-                (uniform-deletion umad-rate)))
+                (uniform-addition [:gap] (:bmx-gap-change-probability argmap))
+                (uniform-gap-deletion (:bmx-gap-change-probability argmap))))
        ;
          :rumad ;; responsive UMAD, uses a deletion rate computed from the actual
                 ;; number of additions made