From 96a3815a3fffbfb41f4460aa22bc4482f77f9ff1 Mon Sep 17 00:00:00 2001 From: Octavian Geagla Date: Fri, 16 Feb 2024 16:07:24 -0700 Subject: [PATCH] More Schemas (#10) * more schema * more schema * more schema * more schema * more schema * more schema * more schema * test cases --- src/closyr/core.clj | 4 +- src/closyr/ops.clj | 26 +++++++---- src/closyr/ops/common.clj | 2 +- src/closyr/ops/eval.clj | 2 +- src/closyr/ops/initialize.clj | 2 +- src/closyr/ops/modify.clj | 2 +- src/closyr/symbolic_regression.clj | 14 +++--- src/closyr/ui/gui.clj | 4 +- src/closyr/util/spec.clj | 44 ++++++++++-------- test/closyr/ops_modify_test.clj | 20 +++++++- test/closyr/ops_test.clj | 58 ++++++++++++++++++++---- test/closyr/symbolic_regression_test.clj | 18 +++++--- test/closyr/util_spec_test.clj | 18 ++++++-- 13 files changed, 154 insertions(+), 60 deletions(-) diff --git a/src/closyr/core.clj b/src/closyr/core.clj index edea976..0183306 100644 --- a/src/closyr/core.clj +++ b/src/closyr/core.clj @@ -3,9 +3,9 @@ (:require [clojure.string :as str] [clojure.tools.cli :as cli] + [closyr.symbolic-regression :as symreg] [closyr.util.csv :as input-csv] - [closyr.util.log :as log] - [closyr.symbolic-regression :as symreg]) + [closyr.util.log :as log]) (:import (java.io File))) diff --git a/src/closyr/ops.clj b/src/closyr/ops.clj index db1ee74..ae88984 100644 --- a/src/closyr/ops.clj +++ b/src/closyr/ops.clj @@ -3,11 +3,11 @@ (:require [clojure.core.async :as async :refer [go go-loop timeout !! ! chan put! take! alts!! alts! close!]] [clojure.string :as str] - [closyr.util.prng :refer :all] - [closyr.util.log :as log] [closyr.ops.common :as ops-common] [closyr.ops.eval :as ops-eval] [closyr.ops.modify :as ops-modify] + [closyr.util.log :as log] + [closyr.util.prng :refer :all] [closyr.util.spec :as specs]) (:import (java.text @@ -95,7 +95,9 @@ (* (abs score) (min 0.1 (* 0.0000001 leafs leafs)))) -(defn- compute-score-from-actuals-and-expecteds +(defn compute-score-from-actuals-and-expecteds + "Compute overall score for fn given some actual and expected ys" + {:malli/schema [:=> [:cat #'specs/GAPhenotype [:vector number?] [:vector number?] number?] number?]} [pheno f-of-xs input-ys-vec leafs] (try (let [abs-resids (map compute-residual input-ys-vec f-of-xs) @@ -116,8 +118,8 @@ (defn score-fn "Symbolic regression scoring" - [{:keys [input-xs-list input-xs-count input-ys-vec - sim-stop-start-chan sim->gui-chan] + {:malli/schema [:=> [:cat #'specs/ScoreFnArgs [:map {:closed false} [:max-leafs number?]] #'specs/GAPhenotype] number?]} + [{:keys [input-xs-list input-xs-count input-ys-vec] :as run-args} {:keys [max-leafs]} pheno] @@ -141,6 +143,13 @@ (defn mutation-fn "Symbolic regression mutation" + {:malli/schema + [:=> + + [:cat [:map {:closed false} [:max-leafs number?]] [:sequential #'specs/GAMutation] #'specs/GAPhenotype #'specs/GAPhenotype] + + #'specs/GAPhenotype]} + [{:keys [max-leafs]} initial-muts p-winner @@ -164,13 +173,14 @@ (assoc new-pheno :mods-applied iters)) (catch Exception e - (log/error "Err in mutation: " (or (.getMessage e) e))))) + (log/error "Err in mutation: " (or (.getMessage e) e)) + (assoc p-winner :util (:util p-discard))))) (defn crossover-fn "Symbolic regression crossover" [{:keys [max-leafs] - :as run-args} + :as run-config} initial-muts p p-discard] @@ -179,7 +189,7 @@ (swap! sim-stats* update-in [:crossovers :counts] #(inc (or % 0)))) (or crossover-result - (mutation-fn run-args initial-muts p p-discard)))) + (mutation-fn run-config initial-muts p p-discard)))) (defn- sort-population diff --git a/src/closyr/ops/common.clj b/src/closyr/ops/common.clj index 027cc8e..ed9c29b 100644 --- a/src/closyr/ops/common.clj +++ b/src/closyr/ops/common.clj @@ -2,8 +2,8 @@ (:refer-clojure :exclude [rand rand-int rand-nth shuffle]) (:require [clojure.core.async :as async :refer [go go-loop timeout !! ! chan put! take! alts!! alt!! close!]] - [closyr.util.prng :refer :all] [closyr.util.log :as log] + [closyr.util.prng :refer :all] [closyr.util.spec :as specs]) (:import (java.util diff --git a/src/closyr/ops/eval.clj b/src/closyr/ops/eval.clj index ffe1bc7..f7659dd 100644 --- a/src/closyr/ops/eval.clj +++ b/src/closyr/ops/eval.clj @@ -1,7 +1,7 @@ (ns closyr.ops.eval (:require - [closyr.util.log :as log] [closyr.ops.common :as ops-common] + [closyr.util.log :as log] [closyr.util.spec :as specs]) (:import (org.matheclipse.core.eval diff --git a/src/closyr/ops/initialize.clj b/src/closyr/ops/initialize.clj index 76f5824..866ab2e 100644 --- a/src/closyr/ops/initialize.clj +++ b/src/closyr/ops/initialize.clj @@ -1,7 +1,7 @@ (ns closyr.ops.initialize (:require - [closyr.util.log :as log] [closyr.ops.common :as ops-common] + [closyr.util.log :as log] [closyr.util.spec :as specs]) (:import (org.matheclipse.core.expression diff --git a/src/closyr/ops/modify.clj b/src/closyr/ops/modify.clj index 72bb871..6b26628 100644 --- a/src/closyr/ops/modify.clj +++ b/src/closyr/ops/modify.clj @@ -150,7 +150,7 @@ #'specs/GAPhenotype] ;; outputs: - #'specs/GAPhenotype]} + [:maybe #'specs/GAPhenotype]]} [max-leafs {^IAST e1 :expr ^ISymbol x-sym :sym ^ExprEvaluator util :util :as p} {^IAST e2 :expr :as p-discard}] diff --git a/src/closyr/symbolic_regression.clj b/src/closyr/symbolic_regression.clj index abad4e5..120d163 100644 --- a/src/closyr/symbolic_regression.clj +++ b/src/closyr/symbolic_regression.clj @@ -2,12 +2,12 @@ (:require [clojure.core.async :as async :refer [go go-loop timeout !! ! chan put! take! alts!! alts! close!]] [closyr.ga :as ga] - [closyr.util.log :as log] [closyr.ops :as ops] [closyr.ops.common :as ops-common] [closyr.ops.initialize :as ops-init] - [closyr.util.spec :as specs] [closyr.ui.gui :as gui] + [closyr.util.log :as log] + [closyr.util.spec :as specs] [flames.core :as flames] [malli.core :as m] [seesaw.core :as ss]) @@ -355,7 +355,9 @@ nil)))))))) -(defn- ->run-args +(defn ->run-args + "Generate one-time computed args for solver" + {:malli/schema [:=> [:cat map?] #'specs/SolverRunArgs]} [{input-xs-exprs :input-xs-exprs input-xs-vec :input-xs-vec input-ys-vec :input-ys-vec @@ -468,8 +470,8 @@ (init [this] - (specs/validate! "SolverRunConfig" specs/SolverRunConfig run-config) - (specs/validate! "SolverRunArgs" specs/SolverRunArgs run-args) + (specs/validate! "SolverRunConfig" #'specs/SolverRunConfig run-config) + (specs/validate! "SolverRunArgs" #'specs/SolverRunArgs run-args) (let [{:keys [iters initial-phenos initial-muts use-gui?]} run-config start (print-and-save-start-time iters initial-phenos) init-pop (ga/initialize @@ -499,7 +501,7 @@ :final-population population :next-step :wait}) (let [{scores :pop-scores :as ga-result} (ga/evolve population)] - (specs/validate! "GAPopulation" specs/GAPopulationPhenotypes (:pop ga-result)) + (specs/validate! "GAPopulation" #'specs/GAPopulationPhenotypes (:pop ga-result)) (ops/report-iteration iters-to-go iters ga-result run-args run-config) (assoc this :ga-result ga-result :iters-to-go (next-iters iters-to-go scores))))))) diff --git a/src/closyr/ui/gui.clj b/src/closyr/ui/gui.clj index 1a44ab7..0ca19fa 100644 --- a/src/closyr/ui/gui.clj +++ b/src/closyr/ui/gui.clj @@ -2,10 +2,10 @@ (:require [clojure.core.async :as async :refer [go go-loop timeout !! ! chan put! alts!]] [clojure.java.io :as io] - [closyr.util.csv :as input-csv] [closyr.dataset.inputs :as input-data] - [closyr.util.log :as log] [closyr.ui.plot :as plot] + [closyr.util.csv :as input-csv] + [closyr.util.log :as log] [seesaw.behave :as sb] [seesaw.border :as sbr] [seesaw.core :as ss] diff --git a/src/closyr/util/spec.clj b/src/closyr/util/spec.clj index 57324de..389bcfb 100644 --- a/src/closyr/util/spec.clj +++ b/src/closyr/util/spec.clj @@ -32,33 +32,33 @@ #_(defn- disable-validate-instrumentation! - "Turn off all schema checks" - [] - (alter-var-root #'*check-schema* (constantly false)) - (mi/unstrument! - {:filters [(apply mi/-filter-ns (concat (keys (m/function-schemas)) - ['closyr.util.spec-test - 'cursive.tests.runner - 'user]))]})) + "Turn off all schema checks" + [] + (alter-var-root #'*check-schema* (constantly false)) + (mi/unstrument! + {:filters [(apply mi/-filter-ns (concat (keys (m/function-schemas)) + ['closyr.util.spec-test + 'cursive.tests.runner + 'user]))]})) -(def GAPhenotype +(def ^:private GAPhenotype [:map {:closed true} [:id {:optional true} :uuid] [:sym some?] - [:expr {:optional true} any?] + [:expr {:optional true} some?] [:score {:optional true} number?] [:util {:optional true} any?] [:last-op {:optional true} :string] [:mods-applied {:optional true} :int]]) -(def GAPopulationPhenotypes +(def ^:private GAPopulationPhenotypes [:vector #'GAPhenotype]) -(def GAMutation +(def ^:private GAMutation [:map {:closed true} [:op :keyword] @@ -69,7 +69,7 @@ [:replace-expr {:optional true} some?]]) -(def SolverRunConfig +(def ^:private SolverRunConfig [:map {:closed true} [:iters pos-int?] @@ -84,7 +84,7 @@ [:input-ys-exprs [:sequential some?]]]) -(def SolverRunArgs +(def ^:private SolverRunArgs [:map {:closed true} [:sim->gui-chan {:optional true} some?] @@ -92,22 +92,30 @@ [:extended-domain-args map?] [:input-xs-list some?] [:input-xs-count pos-int?] - [:input-xs-vec [:vector double?]] - [:input-ys-vec [:vector double?]] + [:input-xs-vec [:vector number?]] + [:input-ys-vec [:vector number?]] [:input-iters pos-int?] [:initial-phenos [:maybe [:sequential map?]]] [:input-phenos-count [:maybe pos-int?]] [:max-leafs [:maybe pos-int?]]]) -(def SolverEvalArgs +(def ^:private SolverEvalArgs + [:map + {:closed false} + [:input-xs-list some?] + [:input-xs-count pos-int?]]) + + +(def ^:private ScoreFnArgs [:map {:closed false} + [:input-ys-vec [:vector number?]] [:input-xs-list some?] [:input-xs-count pos-int?]]) -(def SolverRunResults +(def ^:private SolverRunResults [:map {:closed true} [:iters-done number?] diff --git a/test/closyr/ops_modify_test.clj b/test/closyr/ops_modify_test.clj index 4431b86..c0c1934 100644 --- a/test/closyr/ops_modify_test.clj +++ b/test/closyr/ops_modify_test.clj @@ -2,10 +2,10 @@ (:require [clojure.core.async :as async :refer [go go-loop timeout !! ! chan put! take! alts!! alt!! close!]] [clojure.test :refer :all] - [closyr.util.prng :as prng] [closyr.ops.common :as ops-common] [closyr.ops.initialize :as ops-init] - [closyr.ops.modify :as ops-modify]) + [closyr.ops.modify :as ops-modify] + [closyr.util.prng :as prng]) (:import (org.matheclipse.core.expression F) @@ -383,6 +383,22 @@ :expr (F/Plus x (F/Times x (F/Cos (F/Subtract x F/C1D2))))}))) (str F/C4)))))))) + + (with-redefs-fn {#'prng/rand-int (fn [maxv] (dec maxv)) + #'prng/rand-nth (fn [coll] (last coll)) + #'ops-modify/check-modification-result (fn [_ _ _] (throw (Exception. "Test exception")))} + (fn [] + (with-redefs [ops-modify/crossover-sampler [:times]] + (let [x (F/Dummy "x")] + (testing "Crossover failure" + (is (= (ops-modify/crossover + 100 + {:sym x + :expr F/C4} + {:sym x + :expr (F/Plus x (F/Times x (F/Cos (F/Subtract x F/C1D2))))}) + nil))))))) + (with-redefs-fn {#'prng/rand-int (fn [maxv] (dec maxv)) #'prng/rand-nth (fn [coll] (last coll))} (fn [] diff --git a/test/closyr/ops_test.clj b/test/closyr/ops_test.clj index 5b4b278..fb4d9f1 100644 --- a/test/closyr/ops_test.clj +++ b/test/closyr/ops_test.clj @@ -1,11 +1,11 @@ (ns closyr.ops-test (:require [clojure.test :refer :all] - [closyr.util.prng :as prng] [closyr.ops :as ops] [closyr.ops.common :as ops-common] [closyr.ops.eval :as ops-eval] - [closyr.ops.modify :as ops-modify]) + [closyr.ops.modify :as ops-modify] + [closyr.util.prng :as prng]) (:import (org.matheclipse.core.expression F) @@ -27,6 +27,17 @@ (ops-common/->phenotype x (F/Subtract (F/Times x x) F/C1D2) nil))) -3.0000147))) + (testing "eval score on too big fn" + (is (= + (ops/score-fn {:input-ys-vec [0 1 2] + :input-xs-list (ops-common/exprs->exprs-list + (ops-common/doubles->exprs [0.5 1.0 2.0])) + :input-xs-count 3} + {:max-leafs 0} + (let [x (F/Dummy "x")] + (ops-common/->phenotype x (F/Subtract (F/Times x x) F/C1D2) nil))) + ops/min-score))) + (testing "eval score on failing function" (is (= (with-redefs-fn {#'ops-eval/eval-vec-pheno (fn [_ _] nil)} @@ -103,12 +114,23 @@ (deftest mutation-fn-test (testing "exception in modify" - (with-redefs-fn {#'ops-modify/apply-modifications - (fn [_ _ _ _ _] (throw (Exception. "Testing failed apply modifications")))} - (fn [] - (is (= - (ops/mutation-fn {:max-leafs 100} [] {} {}) - nil))))) + (let [x (F/Dummy "x")] + (with-redefs-fn {#'ops-modify/apply-modifications + (fn [_ _ _ _ _] (throw (Exception. "Testing failed apply modifications")))} + (fn [] + (is (= + (str (:expr (ops/mutation-fn + {:max-leafs 100} + [{:op :modify-leafs + :leaf-modifier-fn (fn ^IExpr [leaf-count + {^IAST expr :expr ^ISymbol x-sym :sym :as pheno} + ^IExpr ie] + (if (= (.toString ie) "x") + (F/Sin ie) + ie))}] + (ops-common/->phenotype x (F/Subtract x F/C1) nil) + (ops-common/->phenotype x (F/Plus x F/C1D2) nil)))) + "-1+x")))))) (testing "long running mod" (let [x (F/Dummy "x")] @@ -138,6 +160,7 @@ #'prng/rand-nth (fn [coll] (first coll))} (fn [] (with-redefs [ops-modify/crossover-sampler [:plus]] + (testing "simple crossover" (let [x (F/Dummy "x")] (is (= @@ -146,7 +169,24 @@ [] (ops-common/->phenotype x (F/Subtract (F/Times x x) F/C1) nil) (ops-common/->phenotype x (F/Plus (F/Sin x) F/C1D2) nil)))) - "x^2+Sin(x)")))))))) + "x^2+Sin(x)"))))))) + + + (with-redefs-fn {#'prng/rand-int (fn [maxv] (dec maxv)) + #'prng/rand-nth (fn [coll] (first coll)) + #'ops-modify/crossover (fn [_ _ _] nil)} + (fn [] + (with-redefs [ops-modify/crossover-sampler [:plus]] + + (testing "simple crossover failure" + (let [x (F/Dummy "x")] + (is (= + (str (:expr (ops/crossover-fn + {:max-leafs ops/default-max-leafs} + [] + (ops-common/->phenotype x (F/Subtract (F/Times x x) F/C1) nil) + (ops-common/->phenotype x (F/Plus (F/Sin x) F/C1D2) nil)))) + "-1+x^2")))))))) (deftest compute-residuals diff --git a/test/closyr/symbolic_regression_test.clj b/test/closyr/symbolic_regression_test.clj index 8814eb4..8f45de2 100644 --- a/test/closyr/symbolic_regression_test.clj +++ b/test/closyr/symbolic_regression_test.clj @@ -1,11 +1,14 @@ (ns closyr.symbolic-regression-test (:require [clojure.core.async :as async :refer [go go-loop timeout !! ! chan put! take! alts!!]] + [clojure.pprint :as pp] [clojure.test :refer :all] [closyr.ops :as ops] [closyr.ops.common :as ops-common] [closyr.ops.initialize :as ops-init] - [closyr.symbolic-regression :as symreg]) + [closyr.symbolic-regression :as symreg] + [closyr.util.spec :as specs] + [malli.core :as m]) (:import (java.awt GraphicsEnvironment))) @@ -291,17 +294,20 @@ (deftest derive-log-steps (testing "with basic input" (is (= - (#'symreg/config->log-steps {:iters 100000 :initial-phenos (vec (repeat 10 0))} + (#'symreg/config->log-steps {:iters 100000 + :initial-phenos (vec (repeat 10 0))} {:input-xs-count 10}) 25)) (is (= - (#'symreg/config->log-steps {:iters 1000 :initial-phenos (vec (repeat 10000 0))} + (#'symreg/config->log-steps {:iters 1000 + :initial-phenos (vec (repeat 10000 0))} {:input-xs-count 150}) 2)) (is (= - (#'symreg/config->log-steps {:iters 10 :initial-phenos (vec (repeat 10 0))} + (#'symreg/config->log-steps {:iters 10 + :initial-phenos (vec (repeat 10 0))} {:input-xs-count 10}) 1)))) @@ -319,8 +325,8 @@ (testing "sufficient args" (is (= (set (keys (#'symreg/->run-args {:input-xs-exprs symreg/example-input-xs-exprs - :input-xs-vec (range (count symreg/example-input-xs-exprs)) - :input-ys-vec (range (count symreg/example-input-xs-exprs)) + :input-xs-vec (vec (range (count symreg/example-input-xs-exprs))) + :input-ys-vec (vec (range (count symreg/example-input-xs-exprs))) :iters 1 :input-phenos-count 1}))) #{:extended-domain-args diff --git a/test/closyr/util_spec_test.clj b/test/closyr/util_spec_test.clj index be18e74..c5db94f 100644 --- a/test/closyr/util_spec_test.clj +++ b/test/closyr/util_spec_test.clj @@ -1,10 +1,11 @@ (ns closyr.util-spec-test (:require - [clojure.pprint :as pp] + [clojure.string :as str] [clojure.test :refer :all] [closyr.util.spec :as specs] [malli.core :as m] - [malli.instrument :as mi])) + [malli.instrument :as mi] + [malli.transform :as mt])) (deftest defined-schemas @@ -65,7 +66,17 @@ (is (= (reduce + 0 (map (fn [[k v]] (count v)) ss)) ;; the number of total defns which have malli/schema metadata in entire src: - 10)))))) + 14)))))) + + +#_(deftest decode-test + (testing "example" + (is (= + (m/decode + [:vector {:decode/string #(str/split % #",")} int?] + "1,2,3,4" + (mt/string-transformer)) + [1 2 3 4])))) #_(deftest check-can-uninstrument @@ -87,3 +98,4 @@ (is (= (ops-common/extend-xs [0.1 "a"]) []))))) +