diff --git a/src/propeller/core.clj b/src/propeller/core.clj index 704485f..342c8a7 100644 --- a/src/propeller/core.clj +++ b/src/propeller/core.clj @@ -1,5 +1,6 @@ (ns propeller.core (:gen-class) + (:require [propeller.push.instructions boolean char code input-output numeric random string]) (:use propeller.gp propeller.push.instructions [propeller.problems simple-regression string-classification])) @@ -7,19 +8,18 @@ (defn -main "Runs propel-gp, giving it a map of arguments." [& args] - (binding [*ns* (the-ns 'propeller.core)] - (gp (update-in (merge {:instructions default-instructions - :error-function regression-error-function - :max-generations 500 - :population-size 500 - :max-initial-plushy-size 50 - :step-limit 100 - :parent-selection :lexicase - :tournament-size 5 - :umad-rate 0.1 - :variation {:umad 0.5 :crossover 0.5} - :elitism false} - (apply hash-map - (map read-string args))) - [:error-function] - #(if (fn? %) % (eval %)))))) + (gp (update-in (merge {:instructions default-instructions + :error-function regression-error-function + :max-generations 500 + :population-size 500 + :max-initial-plushy-size 50 + :step-limit 100 + :parent-selection :lexicase + :tournament-size 5 + :umad-rate 0.1 + :variation {:umad 0.5 :crossover 0.5} + :elitism false} + (apply hash-map + (map read-string args))) + [:error-function] + #(if (fn? %) % (eval %))))) diff --git a/src/propeller/push/instructions.clj b/src/propeller/push/instructions.clj index be28c9d..0d2024f 100644 --- a/src/propeller/push/instructions.clj +++ b/src/propeller/push/instructions.clj @@ -1,16 +1,61 @@ (ns propeller.push.instructions - (:use propeller.push.state) - (:require [tools.character :as char])) + (:require [propeller.push.state :refer [get-args-from-stacks + push-to-stack]])) ;; ============================================================================= ;; PushGP Instructions ;; +;; Instructions are represented as keywords, and stored in an atom. +;; ;; Instructions must all be either functions that take one Push state and ;; return another, or constant literals. ;; ;; TMH: ERCs? ;; ============================================================================= +;; Set of original propel instructions +(def default-instructions + (list :in1 + :integer_+ + :integer_- + :integer_* + :integer_% + :integer_= + :exec_dup + :exec_if + :boolean_and + :boolean_or + :boolean_not + :boolean_= + :string_= + :string_take + :string_drop + :string_reverse + :string_concat + :string_length + :string_includes? + 'close + 0 + 1 + true + false + "" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "A" + "C" + "G" + "T")) + +(def instruction-table (atom (hash-map))) + +(defmacro def-instruction + [instruction definition] + `(swap! instruction-table assoc '~instruction ~definition)) + +;; Number of blocks opened by instructions (default = 0) +(def opens {:exec_dup 1 + :exec_if 2}) + (defn make-instruction "A utility function for making Push instructions. Takes a state, a function to apply to the args, the stacks to take the args from, and the stack to @@ -23,272 +68,3 @@ (let [result (apply function (:args popped-args)) new-state (:state popped-args)] (push-to-stack new-state return-stack result))))) - -;; Original propel instructions -(def default-instructions - (list - 'in1 - 'integer_+ - 'integer_- - 'integer_* - 'integer_% - 'integer_= - 'exec_dup - 'exec_if - 'boolean_and - 'boolean_or - 'boolean_not - 'boolean_= - 'string_= - 'string_take - 'string_drop - 'string_reverse - 'string_concat - 'string_length - 'string_includes? - 'close - 0 - 1 - true - false - "" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "A" - "C" - "G" - "T")) - -;; Number of blocks opened by instructions (default = 0) -(def opens - {'exec_dup 1 - 'exec_if 2}) - -;; ============================================================================= -;; BOOLEAN -;; ============================================================================= - -(defn boolean_= - "Pushes TRUE if the top two BOOLEANs are equal, and FALSE otherwise." - [state] - (make-instruction state = [:boolean :boolean] :boolean)) - -(defn boolean_and - [state] - "Pushes the logical AND of the top two BOOLEANs." - (make-instruction state #(and %1 %2) [:boolean :boolean] :boolean)) - -(defn boolean_or - [state] - "Pushes the logical OR of the top two BOOLEANs." - (make-instruction state #(or %1 %2) [:boolean :boolean] :boolean)) - -(defn boolean_not - [state] - "Pushes the logical NOT of the top BOOLEAN." - (make-instruction state not [:boolean] :boolean)) - -(defn boolean_xor - [state] - "Pushes the logical XOR of the top two BOOLEANs." - (make-instruction state #(or (and %1 (not %2)) - (and (not %1) %2)) - [:boolean :boolean] - :boolean)) - -(defn boolean_invert_first_then_and - [state] - "Pushes the logical AND of the top two BOOLEANs, after applying NOT to the - first one." - (make-instruction state #(and %1 (not %2)) [:boolean :boolean] :boolean)) - -(defn boolean_invert_second_then_and - [state] - "Pushes the logical AND of the top two BOOLEANs, after applying NOT to the - second one." - (make-instruction state #(and (not %1) %2) [:boolean :boolean] :boolean)) - -(defn boolean_fromfloat - [state] - "Pushes FALSE if the top FLOAT is 0.0, and TRUE otherwise." - (make-instruction state #(not (zero? %)) [:float] :boolean)) - -(defn boolean_frominteger - [state] - "Pushes FALSE if the top INTEGER is 0, and TRUE otherwise." - (make-instruction state #(not (zero? %)) [:integer] :boolean)) - -;; ============================================================================= -;; CHAR -;; ============================================================================= - -(defn char_isletter - "Pushes TRUE onto the BOOLEAN stack if the popped character is a letter." - [state] - (make-instruction state char/is-letter [:char] :boolean)) - -(defn char_isdigit - "Pushes TRUE onto the BOOLEAN stack if the popped character is a digit." - [state] - (make-instruction state char/is-digit [:char] :boolean)) - -(defn char_iswhitespace - "Pushes TRUE onto the BOOLEAN stack if the popped character is whitespace - (newline, space, or tab)." - [state] - (make-instruction state char/is-whitespace [:char] :boolean)) - -(defn char_allfromstring - "Pops the STRING stack and pushes the top element's constituent characters - onto the CHAR stack, in order. For instance, \"hello\" will result in the - top of the CHAR stack being o l l e h." - [state] - (make-instruction state #(map char %) [:string] :char)) - -(defn char_frominteger - "Pops the INTEGER stack and pushes the top element's corresponding ASCII - value onto the CHAR stack. Integers larger than 128 will be reduced modulo - 128. For instance, 248 will result in x being pushed." - [state] - (make-instruction state #(char (mod % 128)) [:integer] :char)) - -(defn char_fromfloat - "Pops the FLOAT stack, converts the top item to a whole number, and pushes - its corresponding ASCII value onto the CHAR stack. Whole numbers larger than - 128 will be reduced modulo 128. For instance, 248.45 will result in x being - pushed." - [state] - (make-instruction state #(char (mod (long %) 128)) [:float] :char)) - -;; ============================================================================= -;; CODE -;; ============================================================================= - -;; ...to be added - -;; ============================================================================= -;; EXEC -;; ============================================================================= - -(defn exec_dup - [state] - (if (empty-stack? state :exec) - state - (push-to-stack state :exec (first (:exec state))))) - -(defn exec_if - [state] - (make-instruction state #(if %1 %3 %2) [:boolean :exec :exec] :exec)) - -;; ============================================================================= -;; ENVIRONMENT -;; ============================================================================= - -;; ...to be added - -;; ============================================================================= -;; GENETIC TURING MACHINE -;; ============================================================================= - -;; ...to be added - -;; ============================================================================= -;; GENOME -;; ============================================================================= - -;; ...to be added - -;; ============================================================================= -;; INPUT-OUTPUT -;; ============================================================================= - -(defn in1 - "Pushes the input labeled :in1 on the inputs map onto the :exec stack." - [state] - (push-to-stack state :exec (:in1 (:input state)))) - -;; ============================================================================= -;; INTEGER AND FLOAT -;; ============================================================================= - -(defn integer_= - [state] - (make-instruction state = [:integer :integer] :boolean)) - -(defn integer_+ - [state] - (make-instruction state +' [:integer :integer] :integer)) - -(defn integer_- - [state] - (make-instruction state -' [:integer :integer] :integer)) - -(defn integer_* - [state] - (make-instruction state *' [:integer :integer] :integer)) - -(defn integer_% - [state] - (make-instruction state - (fn [int1 int2] - (if (zero? int2) int1 (quot int1 int2))) - [:integer :integer] - :integer)) - -;; ============================================================================= -;; RANDOM -;; ============================================================================= - -(defn boolean_rand - [state] - "Pushes a random BOOLEAN." - (make-instruction state #(rand-nth [true false]) [] :boolean)) - -;; ============================================================================= -;; STRING -;; ============================================================================= - -(defn string_= - [state] - (make-instruction state = [:string :string] :boolean)) - -(defn string_concat - [state] - (make-instruction state #(apply str (concat %1 %2)) [:string :string] :string)) - -(defn string_drop - [state] - (make-instruction state #(apply str (drop %1 %2)) [:integer :string] :string)) - -(defn string_includes? - [state] - (make-instruction state clojure.string/includes? [:string :string] :boolean)) - -(defn string_length - [state] - (make-instruction state count [:string] :integer)) - -(defn string_reverse - [state] - (make-instruction state #(apply str (reverse %)) [:string] :string)) - -(defn string_take - [state] - (make-instruction state #(apply str (take %1 %2)) [:integer :string] :string)) - -;; ============================================================================= -;; TAG -;; ============================================================================= - -;; ...to be added - -;; ============================================================================= -;; VECTOR -;; ============================================================================= - -;; ...to be added - -;; ============================================================================= -;; ZIP -;; ============================================================================= - -;; ...to be added diff --git a/src/propeller/push/instructions/boolean.clj b/src/propeller/push/instructions/boolean.clj new file mode 100644 index 0000000..866b59f --- /dev/null +++ b/src/propeller/push/instructions/boolean.clj @@ -0,0 +1,62 @@ +(ns propeller.push.instructions.boolean + (:require [propeller.push.instructions :refer [make-instruction + def-instruction]])) + +;; Pushes TRUE if the top two BOOLEANs are equal, and FALSE otherwise +(def-instruction + :boolean_= + (fn [state] + (make-instruction state = [:boolean :boolean] :boolean))) + +;; Pushes the logical AND of the top two BOOLEANs +(def-instruction + :boolean_and + (fn [state] + (make-instruction state #(and %1 %2) [:boolean :boolean] :boolean))) + +;; Pushes the logical OR of the top two BOOLEANs +(def-instruction + :boolean_or + (fn [state] + (make-instruction state #(or %1 %2) [:boolean :boolean] :boolean))) + +;; Pushes the logical NOT of the top BOOLEAN +(def-instruction + :boolean_not + (fn [state] + (make-instruction state not [:boolean] :boolean))) + +;; Pushes the logical XOR of the top two BOOLEAN +(def-instruction + :boolean_xor + (fn [state] + (make-instruction state #(or (and %1 (not %2)) + (and (not %1) %2)) + [:boolean :boolean] + :boolean))) + +;; Pushes the logical AND of the top two BOOLEANs, after applying NOT to the +;; first one +(def-instruction + :boolean_invert_first_then_and + (fn [state] + (make-instruction state #(and %1 (not %2)) [:boolean :boolean] :boolean))) + +;; Pushes the logical AND of the top two BOOLEANs, after applying NOT to the +;; second one +(def-instruction + :boolean_invert_second_then_and + (fn [state] + (make-instruction state #(and (not %1) %2) [:boolean :boolean] :boolean))) + +;; Pushes FALSE if the top FLOAT is 0.0, and TRUE otherwise +(def-instruction + :boolean_fromfloat + (fn [state] + (make-instruction state #(not (zero? %)) [:float] :boolean))) + +;; Pushes FALSE if the top INTEGER is 0, and TRUE otherwise +(def-instruction + :boolean_frominteger + (fn [state] + (make-instruction state #(not (zero? %)) [:integer] :boolean))) diff --git a/src/propeller/push/instructions/char.clj b/src/propeller/push/instructions/char.clj new file mode 100644 index 0000000..8a82f4c --- /dev/null +++ b/src/propeller/push/instructions/char.clj @@ -0,0 +1,48 @@ +(ns propeller.push.instructions.char + (:require [propeller.push.instructions :refer [make-instruction + def-instruction]] + [tools.character :as char])) + +;; Pushes TRUE onto the BOOLEAN stack if the popped character is a letter +(def-instruction + :char_isletter + (fn [state] + (make-instruction state char/is-letter [:char] :boolean))) + +;; Pushes TRUE onto the BOOLEAN stack if the popped character is a digit +(def-instruction + :char_isdigit + (fn [state] + (make-instruction state char/is-digit [:char] :boolean))) + +;; Pushes TRUE onto the BOOLEAN stack if the popped character is whitespace +;; (newline, space, or tab) +(def-instruction + :char_iswhitespace + (fn [state] + (make-instruction state char/is-whitespace [:char] :boolean))) + +;; Pops the STRING stack and pushes the top element's constituent characters +;; onto the CHAR stack, in order. For instance, "hello" will result in the +;; top of the CHAR stack being o l l e h +(def-instruction + :char_allfromstring + (fn [state] + (make-instruction state #(map char %) [:string] :char))) + +;; Pops the INTEGER stack and pushes the top element's corresponding ASCII +;; value onto the CHAR stack. Integers larger than 128 will be reduced modulo +;; 128. For instance, 248 will result in x being pushed +(def-instruction + :char_frominteger + (fn [state] + (make-instruction state #(char (mod % 128)) [:integer] :char))) + +;; Pops the FLOAT stack, converts the top item to a whole number, and pushes +;; its corresponding ASCII value onto the CHAR stack. Whole numbers larger than +;; 128 will be reduced modulo 128. For instance, 248.45 will result in x being +;; pushed. +(def-instruction + :char_fromfloat + (fn [state] + (make-instruction state #(char (mod (long %) 128)) [:float] :char))) diff --git a/src/propeller/push/instructions/code.clj b/src/propeller/push/instructions/code.clj new file mode 100644 index 0000000..deb2636 --- /dev/null +++ b/src/propeller/push/instructions/code.clj @@ -0,0 +1,16 @@ +(ns propeller.push.instructions.code + (:require [propeller.push.state :as state] + [propeller.push.instructions :refer [make-instruction + def-instruction]])) + +(def-instruction + :exec_dup + (fn [state] + (if (state/empty-stack? state :exec) + state + (state/push-to-stack state :exec (first (:exec state)))))) + +(def-instruction + :exec_if + (fn [state] + (make-instruction state #(if %1 %3 %2) [:boolean :exec :exec] :exec))) diff --git a/src/propeller/push/instructions/input_output.clj b/src/propeller/push/instructions/input_output.clj new file mode 100644 index 0000000..2271a0d --- /dev/null +++ b/src/propeller/push/instructions/input_output.clj @@ -0,0 +1,9 @@ +(ns propeller.push.instructions.input-output + (:require [propeller.push.state :as state] + [propeller.push.instructions :refer [def-instruction]])) + +;; Pushes the input labeled :in1 on the inputs map onto the :exec stack +(def-instruction + :in1 + (fn [state] + (state/push-to-stack state :exec (:in1 (:input state))))) diff --git a/src/propeller/push/instructions/numeric.clj b/src/propeller/push/instructions/numeric.clj new file mode 100644 index 0000000..682795c --- /dev/null +++ b/src/propeller/push/instructions/numeric.clj @@ -0,0 +1,32 @@ +(ns propeller.push.instructions.numeric + (:require [propeller.push.instructions :refer [make-instruction + def-instruction]])) + +(def-instruction + :integer_= + (fn [state] + (make-instruction state = [:integer :integer] :boolean))) + +(def-instruction + :integer_+ + (fn [state] + (make-instruction state +' [:integer :integer] :integer))) + +(def-instruction + :integer_- + (fn [state] + (make-instruction state -' [:integer :integer] :integer))) + +(def-instruction + :integer_* + (fn [state] + (make-instruction state *' [:integer :integer] :integer))) + +(def-instruction + :integer_% + (fn [state] + (make-instruction state + (fn [int1 int2] + (if (zero? int2) int1 (quot int1 int2))) + [:integer :integer] + :integer))) diff --git a/src/propeller/push/instructions/random.clj b/src/propeller/push/instructions/random.clj new file mode 100644 index 0000000..4bd7c59 --- /dev/null +++ b/src/propeller/push/instructions/random.clj @@ -0,0 +1,9 @@ +(ns propeller.push.instructions.random + (:require [propeller.push.instructions :refer [def-instruction + make-instruction]])) + +;; Pushes a random BOOLEAN +(def-instruction + :boolean_rand + (fn [state] + (make-instruction state #(rand-nth [true false]) [] :boolean))) diff --git a/src/propeller/push/instructions/string.clj b/src/propeller/push/instructions/string.clj new file mode 100644 index 0000000..5c63af0 --- /dev/null +++ b/src/propeller/push/instructions/string.clj @@ -0,0 +1,38 @@ +(ns propeller.push.instructions.string + (:require [propeller.push.instructions :refer [def-instruction + make-instruction]])) + +(def-instruction + :string_= + (fn [state] + (make-instruction state = [:string :string] :boolean))) + +(def-instruction + :string_concat + (fn [state] + (make-instruction state #(apply str (concat %1 %2)) [:string :string] :string))) + +(def-instruction + :string_drop + (fn [state] + (make-instruction state #(apply str (drop %1 %2)) [:integer :string] :string))) + +(def-instruction + :string_includes? + (fn [state] + (make-instruction state clojure.string/includes? [:string :string] :boolean))) + +(def-instruction + :string_length + (fn [state] + (make-instruction state count [:string] :integer))) + +(def-instruction + :string_reverse + (fn [state] + (make-instruction state #(apply str (reverse %)) [:string] :string))) + +(def-instruction + :string_take + (fn [state] + (make-instruction state #(apply str (take %1 %2)) [:integer :string] :string))) diff --git a/src/propeller/push/interpreter.clj b/src/propeller/push/interpreter.clj index 89f8c69..7a6e616 100644 --- a/src/propeller/push/interpreter.clj +++ b/src/propeller/push/interpreter.clj @@ -1,14 +1,15 @@ (ns propeller.push.interpreter - (:use [propeller.push state instructions])) + (:require [propeller.push.instructions :refer [instruction-table]]) + (:require [propeller.push.state :refer :all])) (defn interpret-one-step "Takes a Push state and executes the next instruction on the exec stack." [state] (let [popped-state (pop-stack state :exec) - first-raw (first (:exec state)) - first-instruction (if (symbol? first-raw) - (var-get (resolve first-raw)) - first-raw)] + first-instruction-raw (first (:exec state)) + first-instruction (if (keyword? first-instruction-raw) + (first-instruction-raw @instruction-table) + first-instruction-raw)] (cond (fn? first-instruction) (first-instruction popped-state) @@ -27,7 +28,7 @@ ; :else (throw (Exception. (str "Unrecognized Push instruction in program: " - first-instruction)))))) + (name first-instruction-raw))))))) (defn interpret-program "Runs the given problem starting with the stacks in start-state." diff --git a/src/propeller/push/utils.clj b/src/propeller/push/utils.clj new file mode 100644 index 0000000..f360500 --- /dev/null +++ b/src/propeller/push/utils.clj @@ -0,0 +1 @@ +(ns propeller.push.utils)