Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
0bee1deaf8
Bump minimist from 1.2.5 to 1.2.8
Bumps [minimist](https://github.com/minimistjs/minimist) from 1.2.5 to 1.2.8.
- [Release notes](https://github.com/minimistjs/minimist/releases)
- [Changelog](https://github.com/minimistjs/minimist/blob/main/CHANGELOG.md)
- [Commits](https://github.com/minimistjs/minimist/compare/v1.2.5...v1.2.8)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-06 14:53:17 +00:00
146 changed files with 856 additions and 5158 deletions

2
.gitignore vendored
View File

@ -23,7 +23,6 @@ notes
/temp /temp
*~ *~
q q
scratch.clj
# Clojure Script # Clojure Script
.shadow-cljs/ .shadow-cljs/
@ -34,5 +33,4 @@ node_modules/
# https://github.com/thelmuth/program-synthesis-benchmark-datasets # https://github.com/thelmuth/program-synthesis-benchmark-datasets
/data /data
**/.DS_Store **/.DS_Store
*.edn
/.cpcache/ /.cpcache/

View File

@ -2,86 +2,37 @@
Yet another Push-based genetic programming system in Clojure. Yet another Push-based genetic programming system in Clojure.
Propeller is a component of several active research projects and it is subject to change as a part of these activities. Full documentation is on the GitHub pages link.
See the git commit comments for guidance with respect to recent changes.
Some documentation is available at [https://lspector.github.io/propeller/](https://lspector.github.io/propeller/), but this should be evaluated in the context of the commit messages and current source code.
## About this fork
This is mainly for personal use to develop inside of Nix environment. Check out the original on package on [github](https://github.com/lspector/propeller).
### How to jack into the project
Once you've ran `nix develop` and vscodium has appeared, click the REPL
button in the bottom left of the screen. Click
`Start your project with a REPL and connect (a.k.a Jack-in)`, select
`Leiningen`, and don't select a profile, instead click `OK`. A window should appear to the right with Calva's REPL.
Head over to `src/propeller/session.cljc` as described below and click
`ctrl + alt + c` then `Enter` to source the session file.
## Usage ## Usage
If you are working in a Clojure IDE with an integrated REPL, the first If you have installed [leiningen](https://leiningen.org), which is a tool
thing you may want to do is to open `src/propeller/session.cljc` and for running Clojure programs, then you can run Propeller on a genetic
evaluate the namespace declaration and the commented-out expressions programming problem that is defined within this project from the command
therein. These demonstrate core components of Propeller including line with the command `lein run -m <namespace>`, replacing `<namespace>`
complete genetic programming runs. When conducting complete genetic
programming runs this way (using `gp/gp`), depending on your IDE you
may need to explicitly open and load the problem file before evaluating
the calls to `require` and `gp/gp`.
To run Propeller from the command line, on a genetic programming problem
that is defined within this project, you will probably want to use either
the Clojure [CLI tools](https://clojure.org/guides/deps_and_cli) or
[leiningen](https://leiningen.org). In the examples below, the leiningen
and CLI commands are identical except that the former begin with
`lein run -m`, while the latter begin with `clj -M -m`.
To start a run use `clj -M -m <namespace>` or
`lein run -m <namespace>`, replacing `<namespace>`
with the actual namespace that you will find at the top of the problem file. with the actual namespace that you will find at the top of the problem file.
For example, you can run the integer-regression genetic programming problem with: For example, you can run the simple-regression genetic programming problem with:
``` ```
clj -M -m propeller.problems.regression.integer-regression lein run -m propeller.problems.simple-regression
```
or
```
lein run -m propeller.problems.regression.integer-regression
``` ```
Additional command-line arguments may Additional command-line arguments may
be provided to override the default key/value pairs specified in the be provided to override the default key/value pairs specified in the
problem file, for example: problem file, for example:
```
clj -M -m propeller.problems.regression.integer-regression :population-size 100
```
or
``` ```
lein run -m propeller.problems.regression.integer-regression :population-size 100 lein run -m propeller.problems.simple-regression :population-size 100
``` ```
On Unix operating systems, including MacOS, you can use something On Unix operating systems, including MacOS, you can use something
like the following to send output both to the terminal like the following to send output both to the terminal
and to a text file (called `outfile` in this example): and to a text file (called `outfile` in this example):
``` ```
clj -M -m propeller.problems.regression.integer-regression | tee outfile lein run -m propeller.problems.simple-regression | tee outfile
```
or
```
lein run -m propeller.problems.regression.integer-regression | tee outfile
``` ```
If you want to provide command line arguments that include If you want to provide command line arguments that include
@ -92,22 +43,24 @@ value for the `:variation` argument, which is a clojure map
containing curly brackets that may confuse your shell: containing curly brackets that may confuse your shell:
``` ```
clj -M -m propeller.problems.regression.integer-regression :variation "{:umad 1.0}" lein run -m propeller.problems.simple-regression :variation "{:umad 1.0}"
``` ```
or To run a genetic programming problem from a REPL, start
your REPL for the project (e.g. with `lein repl` at the
command line when in the project directory, or through your
IDE) and then do something like the following (which in
this case runs the simple-regression problem with
`:population-size` 100):
``` ```
lein run -m propeller.problems.regression.integer-regression :variation "{:umad 1.0}" (require 'propeller.problems.simple-regression)
(in-ns 'propeller.problems.simple-regression)
(-main :population-size 100 :variation {:umad 1.0})
``` ```
For many genetic operator hyperparameters, collections may be provided in place of single values. When this is done, a random element of the collection will be chosen (with each being equally likely) each time the operator is used. When specied at the command line, these collections will also have to be quoted, for example with `:umad-rate "[0.01 0.05 0.1]"` to mean that UMAD rates of 0.01, 0.05, and 0.1 can be used. If you want to run the problem with the default parameters,
then you should call `-main` without arguments, as `(-main)`.
By default, Propeller will conduct many processes concurrently on multiple
cores using threads. If you want to disable this behavior (for example, during
debugging) then provide the argument `:single-thread-mode` with the value `true`.
Threads are not available in Javascript, so no processes are run concurrnetly
when Propeller is run in Clojurescript.
## CLJS Usage ## CLJS Usage
@ -143,9 +96,8 @@ Calling `(-main)` will run the default genetic programming problem.
## Description ## Description
Propeller is an implementation of the Push programming Propel is an implementation of the Push programming
language and the PushGP genetic programming system in Clojure, based language and the PushGP genetic programming system in Clojure.
on Tom Helmuth's little PushGP implementation [propel](https://github.com/thelmuth/propel).
For more information on Push and PushGP see For more information on Push and PushGP see
[http://pushlanguage.org](http://pushlanguage.org). [http://pushlanguage.org](http://pushlanguage.org).

View File

@ -3,8 +3,7 @@
{org.clojure/clojure #:mvn{:version "1.10.0"}, {org.clojure/clojure #:mvn{:version "1.10.0"},
org.clojure/clojurescript #:mvn{:version "1.9.946"}, org.clojure/clojurescript #:mvn{:version "1.9.946"},
org.clojure/test.check #:mvn{:version "1.1.0"}, org.clojure/test.check #:mvn{:version "1.1.0"},
net.clojars.schneau/psb2 #:mvn{:version "1.1.1"} net.clojars.schneau/psb2 #:mvn{:version "1.1.0"}},
org.clojure/data.csv #:mvn{:version "1.0.1"}},
:mvn/repos {} :mvn/repos {}
:codox {:extra-deps {codox/codox {:mvn/version "0.10.8"}} :codox {:extra-deps {codox/codox {:mvn/version "0.10.8"}}
:exec-fn codox.main/generate-docs :exec-fn codox.main/generate-docs

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

27
flake.lock generated
View File

@ -1,27 +0,0 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1741600792,
"narHash": "sha256-yfDy6chHcM7pXpMF4wycuuV+ILSTG486Z/vLx/Bdi6Y=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "ebe2788eafd539477f83775ef93c3c7e244421d3",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-24.11",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

View File

@ -1,42 +0,0 @@
{
description = "A flake for developing in a clojure environment with vscode";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11";
};
outputs = { nixpkgs, ... }:
let system = "x86_64-linux"; in
{
devShells."${system}".default =
let
pkgs = nixpkgs.legacyPackages."${system}";
in
pkgs.mkShellNoCC {
buildInputs = [ pkgs.bashInteractive ];
packages = with pkgs; [
clojure
clojure-lsp
leiningen
nodejs
jdk8
(vscode-with-extensions.override {
vscode = vscodium;
vscodeExtensions = pkgs.vscode-utils.extensionsFromVscodeMarketplace [
{
name = "calva";
publisher = "betterthantomorrow";
version = "2.0.490";
sha256 = "sha256-PCEzSWahrTHXeGFrFShvbXrnoq3AtuVkoohKLxBGDRA=";
}
];
})
];
shellHook = ''
export SHELL=${pkgs.lib.getExe pkgs.bashInteractive}
echo "starting codium"
codium .
'';
};
};
}

17
package-lock.json generated
View File

@ -723,10 +723,13 @@
"dev": true "dev": true
}, },
"node_modules/minimist": { "node_modules/minimist": {
"version": "1.2.5", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true "dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
}, },
"node_modules/mkdirp": { "node_modules/mkdirp": {
"version": "0.5.5", "version": "0.5.5",
@ -1931,9 +1934,9 @@
"dev": true "dev": true
}, },
"minimist": { "minimist": {
"version": "1.2.5", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true "dev": true
}, },
"mkdirp": { "mkdirp": {

View File

@ -1,4 +1,4 @@
(defproject net.clojars.lspector/propeller "0.3.2" (defproject net.clojars.lspector/propeller "0.3.0"
:description "Yet another Push-based genetic programming system in Clojure." :description "Yet another Push-based genetic programming system in Clojure."
:url "https://github.com/lspector/propeller" :url "https://github.com/lspector/propeller"
:license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
@ -6,8 +6,7 @@
:dependencies [[org.clojure/clojure "1.10.0"] :dependencies [[org.clojure/clojure "1.10.0"]
[org.clojure/clojurescript "1.9.946"] [org.clojure/clojurescript "1.9.946"]
[org.clojure/test.check "1.1.0"] [org.clojure/test.check "1.1.0"]
[net.clojars.schneau/psb2 "1.1.1"] [net.clojars.schneau/psb2 "1.1.1"]]
[org.clojure/data.csv "1.0.1"]]
:profiles {:profiling {:dependencies [[com.clojure-goes-fast/clj-async-profiler "0.5.1"]]}} :profiles {:profiling {:dependencies [[com.clojure-goes-fast/clj-async-profiler "0.5.1"]]}}
:main ^:skip-aot propeller.core :main ^:skip-aot propeller.core
:repl-options {:init-ns propeller.core} :repl-options {:init-ns propeller.core}

1
scripts/GenerateDocs.sh Executable file → Normal file
View File

@ -1,6 +1,5 @@
#!/bin/sh #!/bin/sh
pip install mdutils
lein codox lein codox
python3 FunctionsToMD.py python3 FunctionsToMD.py
python3 HTMLFix.py python3 HTMLFix.py

View File

@ -39,9 +39,9 @@ line with the command `lein run -m <namespace>`, replacing `<namespace>`
with the actual namespace that you will find at the top of the problem file. with the actual namespace that you will find at the top of the problem file.
If you have installed [Clojure](https://clojure.org/guides/install_clojure#java), you can run Propeller on a genetic programming If you have installed [Clojure](https://clojure.org/guides/install_clojure#java), you can run Propeller on a genetic programming
problem with the command `clj -M -m <namespace>`, replacing `<namespace>` with problem with the command `clj --main <namespace>`, replacing `<namespace>` with
the actual namespace that you will find at the top of the problem file. the actual namespace that you will find at the top of the problem file.
The examples below use leiningen, but you can replace `lein run -m` with `clj -M -m` to run the same problem. The examples below use leiningen, but you can replace `lein run -m` with `clj --main` to run the same problem.
A specific example is provided later below. A specific example is provided later below.

View File

@ -116,6 +116,7 @@ instructions from `push/instructions`, input instructions, close, and constants
:umad-rate 0.1 :umad-rate 0.1
:variation {:umad 1.0 :crossover 0.0} :variation {:umad 1.0 :crossover 0.0}
:elitism false} :elitism false}
(apply hash-map (map #(if (string? %) (read-string %) %) args))))) (apply hash-map (map #(if (string? %) (read-string %) %) args))))
(#?(:clj shutdown-agents)))
``` ```

View File

@ -7,10 +7,8 @@ Table of contents
* [Additional Instructions](#additional-instructions) * [Additional Instructions](#additional-instructions)
* [input_output.cljc](#input_outputcljc) * [input_output.cljc](#input_outputcljc)
* [.DS_Store](#ds_store)
* [numeric.cljc](#numericcljc) * [numeric.cljc](#numericcljc)
* [string.cljc](#stringcljc) * [string.cljc](#stringcljc)
* [parentheses.cljc](#parenthesescljc)
* [character.cljc](#charactercljc) * [character.cljc](#charactercljc)
* [bool.cljc](#boolcljc) * [bool.cljc](#boolcljc)
* [code.cljc](#codecljc) * [code.cljc](#codecljc)
@ -21,12 +19,8 @@ Table of contents
## :print_newline ## :print_newline
Prints new line Prints new line
# .DS_Store
# numeric.cljc # numeric.cljc
## :float_div
Divides the top two items on the float stack If denominator is 0, returns 1.0
## :float_cos ## :float_cos
Pushes the cosine of the top FLOAT Pushes the cosine of the top FLOAT
## :float_sin ## :float_sin
@ -99,8 +93,6 @@ Splits the top STRING on whitespace, and pushes back the resulting components in
Pushes the substring of the top STRING, with beginning and end indices determined by the second topmost and topmost INTEGERs respectively. If an index is out of bounds, the beginning/end of the string is used instead Pushes the substring of the top STRING, with beginning and end indices determined by the second topmost and topmost INTEGERs respectively. If an index is out of bounds, the beginning/end of the string is used instead
## :string_take ## :string_take
Pushes the substring of the top STRING consisting of its first n letters, where n is determined by the top INTEGER Pushes the substring of the top STRING consisting of its first n letters, where n is determined by the top INTEGER
# parentheses.cljc
# character.cljc # character.cljc
## :char_is_letter ## :char_is_letter

View File

@ -1,56 +0,0 @@
Downsampling the Training Data
=
Downsampling is a very simple way to improve the efficiency of your evolutionary runs. It might allow for deeper evolutionary searches and a greater success rate.
Using Downsampled selection with propeller is easy:
Set the :parent-selection argument to whichever selection strategy you would like, and set the :downsample? argument to true as follows:
```clojure
lein run -m propeller.problems.simple-regression :parent-selection :lexicase :downsample? true <required downsampling args here>
```
The number of evaluations is held constant when comparing to a full training set run, so set the :max-generations to a number of generations that you would have gone to using a **full** sample.
## Downsample Functions
In this repository, you have access to 3 different downsampling functions. These are the methods used to take a down-sample from the entire training set.
To use them, add the argument ```:ds-function``` followed by which function you would like to us
The list is
- ```:case-maxmin``` - This is the method used for informed down-sampled lexicase selection
- ```:case-maxmin-auto``` - This method automatically determines the downsample size
- ```:case-rand```- Random Sampling
### Using ```:case-maxmin```:
In order to use regular informed down-sampled selection, you must specify a few things:
- ```:downsample-rate```- This is the $r$ parameter: what proportion of the full sample should be in the down-sample $\in [0,1]$
- ```:ds-parent-rate``` - This is the $\rho$ parameter: what proportion of parents are used to evaluate case distances $\in [0,1]$
- ```:ds-parent-gens``` - This is the $k$ parameter: How many generations in between parent evaluations for distances $\in \{1,2,3, \dots\}$
### Using ```:case-maxmin-auto```:
In order to use automatic informed down-sampled selection, you must specify a few things:
- ```:case-delta ```- This is the $\Delta$ parameter: How close can the farthest case be from its closest case before we stop adding to the down-sample
- ```:ids-type``` - Either ```:elite``` or ```:solved ``` - Specifies whether we are using elite/not-elite or solved/not-solved as our binary-fication of case solve vectors.
- ```:ds-parent-rate``` - This is the $\rho$ parameter: what proportion of parents are used to evaluate case distances $\in [0,1]$
- ```:ds-parent-gens``` - This is the $k$ parameter: How many generations in between parent evaluations for distances $\in \{1,2,3, \dots\}$
### Using ```:case-rand```:
In order to use regular randomly down-sampled selection, you must specify a few things:
- ```:downsample-rate```- This is the $r$ parameter: what proportion of the full sample should be in the down-sample $\in [0,1]$
Here's an example of running informed downsampled lexicase selection with $r=0.1$, $\rho=0.01$ and $k=100$ on the simple classification problem:
```clojure
lein run -m propeller.problems.simple-classification :parent-selection :lexicase :downsample? true :ds-function :case-maxmin :downsample-rate 0.1 :max-generations 300 :ds-parent-rate 0.01 :ds-parent-gens 100
```

View File

@ -1,9 +1,6 @@
# Generating Documentation for Propeller # Generating Documentation for Propeller
In order to generate documentation, you'll need Python and `pip` installed. To generate documentation with [codox](https://github.com/weavejester/codox), run `scripts/GenerateDocs.sh`in the command line.
To generate documentation with [codox](https://github.com/weavejester/codox), run `scripts/GenerateDocs.sh` in the command line from the `scripts` directory.
To make the script executable, you may need to first run `chmod +x GenerateDocs.sh`.
This will run "lein codox" on the command line to generate first batch of HTMl files. This will run "lein codox" on the command line to generate first batch of HTMl files.
Then, it runs FunctionsToMD to take Push instructions generated by `def-instruction` and spit it out to a Markdown file. Then, it runs FunctionsToMD to take Push instructions generated by `def-instruction` and spit it out to a Markdown file.
Then, it runs HTMLFix to fix the ordered lists in `Adding_Genetic_Operators.md`, `Adding_Problem.md`, and Then, it runs HTMLFix to fix the ordered lists in `Adding_Genetic_Operators.md`, `Adding_Problem.md`, and

View File

@ -1,132 +0,0 @@
(ns propeller.downsample
(:require [propeller.tools.math :as math]
[propeller.tools.metrics :as metrics]
[propeller.utils :as utils]))
(defn assign-indices-to-data
"assigns an index to each training case in order to differentiate them when downsampling"
[training-data argmap]
(utils/pmapallv (fn [data-map index]
(let [data-m (if (map? data-map) data-map (assoc {} :data data-map))] ;if data is not in a map, make it one
(assoc data-m :index index)))
training-data (range (count training-data)) argmap))
(defn initialize-case-distances
[{:keys [training-data population-size] :as argmap}]
(utils/pmapallv #(assoc % :distances (vec (repeat (count training-data) population-size))) training-data argmap))
(defn select-downsample-random
"Selects a downsample from the training cases and returns it"
[training-data {:keys [downsample-rate]}]
(take (int (* downsample-rate (count training-data))) (shuffle training-data)))
(defn select-downsample-maxmin
"selects a downsample that has it's cases maximally far away by sequentially
adding cases to the downsample that have their closest case maximally far away"
[training-data {:keys [downsample-rate]}]
(let [shuffled-cases (shuffle training-data)
goal-size (int (* downsample-rate (count training-data)))]
(loop [new-downsample (conj [] (first shuffled-cases))
cases-to-pick-from (rest shuffled-cases)]
(if (>= (count new-downsample) goal-size)
new-downsample
(let [tournament cases-to-pick-from
min-case-distances (metrics/min-of-colls
(map (fn [distance-list]
(utils/filter-by-index distance-list (map #(:index %) tournament)))
(map #(:distances %) new-downsample)))
selected-case-index (metrics/argmax min-case-distances)]
(recur (conj new-downsample (nth tournament selected-case-index))
(shuffle (utils/drop-nth selected-case-index tournament))))))))
(defn select-downsample-maxmin-adaptive
"selects a downsample that has it's cases maximally far away by sequentially
adding cases to the downsample that have their closest case maximally far away
automatically stops when the maximum minimum distance is below delta"
[training-data {:keys [case-delta]}]
(let [shuffled-cases (shuffle training-data)]
(loop [new-downsample (conj [] (first shuffled-cases))
cases-to-pick-from (rest shuffled-cases)]
(let [tournament cases-to-pick-from
min-case-distances (metrics/min-of-colls
(map (fn [distance-list]
(utils/filter-by-index distance-list (map #(:index %) tournament)))
(map #(:distances %) new-downsample)))
selected-case-index (metrics/argmax min-case-distances)]
(if (or (= 0 (count tournament)) (<= (apply max min-case-distances) case-delta))
new-downsample
(recur (conj new-downsample (nth tournament selected-case-index))
(shuffle (utils/drop-nth selected-case-index tournament))))))))
(defn get-distance-between-cases
"returns the distance between two cases given a list of individual error vectors, and the index these
cases exist in the error vector. Only makes the distinction between zero and nonzero errors"
[error-lists case-index-1 case-index-2]
(if (or (< (count (first error-lists)) case-index-1)
(< (count (first error-lists)) case-index-2)
(neg? case-index-1) (neg? case-index-2))
(count error-lists) ;return the max distance
(let [errors-1 (map #(nth % case-index-1) error-lists)
errors-2 (map #(nth % case-index-2) error-lists)]
;compute distance between errors-1 and errors-2
(reduce + (map (fn [e1 e2] (math/abs (- (math/step e1) (math/step e2)))) errors-1 errors-2)))))
(defn update-at-indices
"merges two vectors at the indices provided by a third vector"
[big-vec small-vec indices]
(->> big-vec
(map-indexed (fn [idx itm] (let [index (.indexOf indices idx)]
(if (not= -1 index) (nth small-vec index) itm))))
vec))
(defn merge-map-lists-at-index
"merges two lists of maps, replacing the maps in the big
list with their corresponding (based on index) maps in the small list"
[big-list small-list]
(map
#(let [corresponding-small (some (fn [c] (when (= (:index %) (:index c)) c)) small-list)]
(if (nil? corresponding-small) % corresponding-small))
big-list))
(defn replace-mins-with-zero
"replaces the minimum value(s) in a list with zero"
[coll]
(if (empty? coll)
'()
(let [m (apply min coll)]
(map #(if (= m %) 0 %) coll))))
(defn replace-close-zero-with-zero
"replaces values within a delta of zero with zero, used for regression problems"
[coll delta]
(map #(if (>= delta %) 0 %) coll))
(defn convert-to-elite-error
"converts a set of errors into a list where all the elite errors are replaced with 0s so that we can use
it in the selection of down-samples with elite/not-elite selection"
[errors]
(map #(replace-mins-with-zero %) errors))
(defn convert-to-soft-error
[errors delta]
(map #(replace-close-zero-with-zero % delta) errors))
(defn update-case-distances
"updates the case distance field of training-data list, should be called after evaluation of individuals
evaluated-pop should be a list of individuals that all have the :errors field with a list of this
individuals performance on the each case in the training-data, in order. ids-type is :elite to use elite/not-elite, :soft to consider near solves, and :solved to use solve/not-solved"
([evaluated-pop ds-data training-data ids-type]
(update-case-distances evaluated-pop ds-data training-data ids-type 0)) ; default solution threshold is 0, only used if ids-type is :soft
([evaluated-pop ds-data training-data ids-type solution-threshold]
(let [ds-indices (map #(:index %) ds-data)
errors (map #(:errors %) evaluated-pop)
corr-errors (case ids-type
:elite (convert-to-elite-error errors)
:soft (convert-to-soft-error errors solution-threshold)
errors)] ;errors, including elite/not-elite distinction
(merge-map-lists-at-index
training-data (map-indexed
(fn [idx d-case] (update-in d-case
[:distances] #(update-at-indices
% (map (fn [other] (get-distance-between-cases corr-errors idx other))
(range (count ds-indices))) ds-indices))) ds-data)))))

View File

@ -2,58 +2,49 @@
"The genetic material in Propeller. A `plushy` is a list of Push instructions that represent a Push program. "The genetic material in Propeller. A `plushy` is a list of Push instructions that represent a Push program.
They hold the genetic material for an `individual`. In the initial population, we create random plushys." They hold the genetic material for an `individual`. In the initial population, we create random plushys."
{:doc/format :markdown} {:doc/format :markdown}
(:require [propeller.push.instructions.parentheses :as parentheses] (:require [propeller.push.instructions :as instructions]
[propeller.utils :as utils])) [propeller.utils :as utils]))
(defn make-random-plushy (defn make-random-plushy
"Creates and returns a new plushy made of random instructions." "Creates and returns a new plushy made of random instructions and of a maximum size of max-initial-plushy-size."
[{:keys [instructions max-initial-plushy-size bmx? bmx-gene-length-limit] [instructions max-initial-plushy-size]
:as argmap}] (repeatedly
(let [plushy (repeatedly (rand-int max-initial-plushy-size) (rand-int max-initial-plushy-size)
#(utils/random-instruction instructions argmap))] #(utils/random-instruction instructions)))
(if bmx?
(-> plushy
(utils/remove-empty-genes)
(utils/enforce-gene-length-limit bmx-gene-length-limit))
plushy)))
(defn plushy->push-internal
[plushy argmap]
(let [opener? #(and (vector? %) (= (first %) 'open))] ;; [open <n>] marks opens
(loop [push () ;; iteratively build the Push program from the plushy
plushy (mapcat #(let [n (get parentheses/opens %)]
(if (and n
(> n 0))
[% ['open n]]
[%]))
plushy)]
(if (empty? plushy) ;; maybe we're done?
(if (some opener? push) ;; done with plushy, but unclosed open
(recur push '(close)) ;; recur with one more close
push) ;; otherwise, really done, return push
(let [i (first plushy)]
(if (= i 'close)
(if (some opener? push) ;; process a close when there's an open
(recur (let [post-open (reverse (take-while (comp not opener?)
(reverse push)))
open-index (- (count push) (count post-open) 1)
num-open (second (nth push open-index))
pre-open (take open-index push)]
(if (= 1 num-open)
(concat pre-open [post-open])
(concat pre-open [post-open ['open (dec num-open)]])))
(rest plushy))
(recur push (rest plushy))) ;; unmatched close, ignore
(recur (concat push [i]) (rest plushy)))))))) ;; anything else
(defn plushy->push (defn plushy->push
"Returns the Push program expressed by the given plushy representation." "Returns the Push program expressed by the given plushy representation.
;; use an empty argmap if none provided
([plushy] The function takes in a plushy representation as input and converts it into a Push program by iteratively processing
(plushy->push plushy {})) the plushy elements and adding instructions to the push program.
;; call plushy->push-internal with possibly-preprocessed plushy It also handles the case where there are open instructions that need to be closed before the end of the program.
"
([plushy] (plushy->push plushy {}))
([plushy argmap] ([plushy argmap]
(plushy->push-internal (if (:bmx? argmap) (let [plushy (if (:diploid argmap) (map first (partition 2 plushy)) plushy)
(filter (complement #{:gap}) plushy) opener? #(and (vector? %) (= (first %) 'open))] ;; [open <n>] marks opens
plushy) (loop [push () ;; iteratively build the Push program from the plushy
argmap))) plushy (mapcat #(let [n (get instructions/opens %)]
(if (and n
(> n 0))
[% ['open n]]
[%]))
plushy)]
(if (empty? plushy) ;; maybe we're done?
(if (some opener? push) ;; done with plushy, but unclosed open
(recur push '(close)) ;; recur with one more close
push) ;; otherwise, really done, return push
(let [i (first plushy)]
(if (= i 'close)
(if (some opener? push) ;; process a close when there's an open
(recur (let [post-open (reverse (take-while (comp not opener?)
(reverse push)))
open-index (- (count push) (count post-open) 1)
num-open (second (nth push open-index))
pre-open (take open-index push)]
(if (= 1 num-open)
(concat pre-open [post-open])
(concat pre-open [post-open ['open (dec num-open)]])))
(rest plushy))
(recur push (rest plushy))) ;; unmatched close, ignore
(recur (concat push [i]) (rest plushy))))))))) ;; anything else

View File

@ -1,10 +1,10 @@
(ns propeller.gp (ns propeller.gp
"Main genetic programming loop." "Main genetic programming loop."
(:require [clojure.string] (:require [clojure.string]
[clojure.pprint]
[propeller.genome :as genome] [propeller.genome :as genome]
[propeller.simplification :as simplification] [propeller.simplification :as simplification]
[propeller.variation :as variation] [propeller.variation :as variation]
[propeller.downsample :as downsample]
[propeller.push.instructions.bool] [propeller.push.instructions.bool]
[propeller.push.instructions.character] [propeller.push.instructions.character]
[propeller.push.instructions.code] [propeller.push.instructions.code]
@ -13,210 +13,88 @@
[propeller.push.instructions.polymorphic] [propeller.push.instructions.polymorphic]
[propeller.push.instructions.string] [propeller.push.instructions.string]
[propeller.push.instructions.vector] [propeller.push.instructions.vector]
[propeller.push.instructions.signal] [propeller.selection :as selection]))
[propeller.selection :as selection]
[propeller.utils :as utils]))
(defn report (defn report
"Reports information each generation." "Reports information for each generation."
[evaluations pop generation argmap training-data] [pop generation argmap]
(let [best (first pop)] (let [best (first pop)]
(utils/pretty-map-println (clojure.pprint/pprint {:generation generation
(merge :best-plushy (:plushy best)
{:generation generation :best-program (genome/plushy->push (:plushy best) argmap)
:best-plushy (:plushy best) :best-total-error (:total-error best)
:best-program (genome/plushy->push (:plushy best) argmap) :best-errors (:errors best)
:best-total-error (:total-error best) :best-behaviors (:behaviors best)
:evaluations evaluations :genotypic-diversity (float (/ (count (distinct (map :plushy pop))) (count pop)))
:ds-indices (if (:downsample? argmap) :behavioral-diversity (float (/ (count (distinct (map :behaviors pop))) (count pop)))
(map #(:index %) training-data) :average-genome-length (float (/ (reduce + (map count (map :plushy pop))) (count pop)))
nil) :average-total-error (float (/ (reduce + (map :total-error pop)) (count pop)))})
:best-errors (:errors best) (println)))
: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)))}
(if (:bmx? argmap)
{:best-gene-count (utils/count-genes (:plushy best))
:average-gene-count (float (/ (reduce + (map utils/count-genes (map :plushy pop)))
(count pop)))}
{})))
(flush)))
(defn cleanup
[]
#?(:clj (shutdown-agents))
(prn {:run-completed true})
nil)
(defn fill-defaults
"Returns argmap with any unspecified values filled with defaults."
[argmap]
(let [defaults
{:alignment-deviation 2 ; for alternation, the standard deviation of deviation of index when alternating
:alternation-rate 0.1 ; for alternation, the probability of switching parents at each location
:bmx-exchange-rate 0.5 ; for bmx, the rate at which genes will be exchanged
:bmx-gene-length-limit 10 ; for bmx, the maximum length of a gene
:bmx-gap-change-probability 0.001 ; for bmx, the mutation rate for gaps
:bmx-complementary? false ; for bmx, whether mates selected using reverse case sequence of first parent
:bmx-maximum-distance 1000000 ; for bmx, don't exchange if distance is greater than this
:bmx-same-gene-count false ; for bmx, only allow exchanges between individuals with same number of genes
:closes :specified ; :specified, :balanced, :none
:custom-report false ; if provided, should be a function that takes an argmap
:dont-end false ; if true, keep running until limit regardless of success
:downsample? true ; wether to use downsampling
:ds-function :case-maxmin ; :case-rand, case-maxmin, case-maxmin-auto
:downsample-rate 0.05 ; proportion of data used in downsample
:ds-parent-rate 0.01 ; proportion of parents used to evaluate case distances
:ds-parent-gens 10 ; generations between computation of parent distances
:elitism false ; whether always to add the lowest-error individual to the next generation
:error-function (fn [& args] (println "ERROR FUNCTION NOT PROVIDED")) ; must provide
:ids-type :solved ; type of informed downsampling, :solved or :elite or :soft
:instructions ["INSTRUCTIONS NOT PROVIDED"] ; must be provided
:max-batch-size 10 ; for motley-batch-lexicase-selection, the max size of a batch of cases
:max-initial-plushy-size 100 ; the maximum size of genomes in initial population
:max-generations 1000 ; generation limi when downsampling is not used, adjusted by downsampling
:parent-selection :lexicase ; see options in variation.cljc
:population-size 1000 ; the size of the GP ppopulation
:replacement-rate 0.1 ; for uniform-replacement, the rate at with items will be replaced
:simplification? false ; whether to auto-simplify solutions
:simplification-k 4 ; when auto-simplifying, max number of items deleted in single step
:simplification-steps 1000 ; when auto-simplifying, number of simplification steps to perform
:simplification-verbose? false ; when auto-simplifying, whether to print a lot of information
:single-thread-mode false ; if true, don't use multithreading
:solution-error-threshold 0 ; maximum total error for solutions
:ssx-not-bmx false ; for bmx, swap with segment with same sequence index, not by best match
:step-limit 1000 ; limit of Push interpreter steps in a Push program evaluation
:testing-data [] ; must be provided unless there is no testing data
:tournament-size 5 ; for torunament selection, the number of individuals in each tournament
:training-data [] ; must be provided
:umad-rate 0.1 ; addition rate (from which deletion rate will be derived) for UMAD
:variation {:umad 1} ; genetic operators and probabilities for their use, which should sum to 1
:tournament-comp-op min-key ; individual error comparison operator for tournament selection (switch to max-key for max)
:lexicase-comp-op min ; individual error comparison operator for lexicase selection (switch to max for max)
:population-sort-comp < ; comparision operator to use for sorting the population
}
defaulted (merge defaults argmap)]
(merge defaulted ; use the map below to include derived values in argmap
{:bmx? (some #{:bmx :bmx-umad} (keys (:variation defaulted)))})))
(defn gp (defn gp
"Main GP function" "Main GP loop.
[non-default-argmap]
(let [argmap (fill-defaults non-default-argmap) On each iteration, it creates a population of random plushies using a mapper
{:keys [population-size max-generations error-function solution-error-threshold dont-end function and genome/make-random-plushy function,
downsample? ds-parent-rate ds-parent-gens ids-type population-sort-comp]} argmap] then it sorts the population by the total error using the error-function
;; print starting args and sort-by function. It then takes the best individual from the sorted population,
(prn {:starting-args (update (update argmap :error-function str) and if the parent selection is set to epsilon-lexicase, it adds the epsilons to the argmap.
:instructions
(fn [instrs] The function then checks if the custom-report argument is set,
(utils/not-lazy (map #(if (fn? %) (str %) %) instrs))))}) if so it calls that function passing the evaluated population,
(println) current generation and argmap. If not, it calls the report function
passing the evaluated population, current generation and argmap.
Then, it checks if the total error of the best individual is less than or equal
to the solution-error-threshold or if the current generation is greater than or
equal to the max-generations specified. If either is true, the function
exits with the best individual or nil. If not, it creates new individuals
for the next generation using the variation/new-individual function and the
repeatedly function, and then continues to the next iteration of the loop. "
[{:keys [population-size max-generations error-function instructions
max-initial-plushy-size solution-error-threshold mapper]
:or {solution-error-threshold 0.0
;; The `mapper` will perform a `map`-like operation to apply a function to every individual
;; in the population. The default is `map` but other options include `mapv`, or `pmap`.
mapper #?(:clj pmap :cljs map)}
:as argmap}]
;; ;;
(loop [generation 0 (prn {:starting-args (update (update argmap :error-function str) :instructions str)})
evaluations 0 (println)
population (utils/pmapallv ;;
(fn [_] {:plushy (genome/make-random-plushy argmap)}) (loop [generation 0
(range population-size) population (mapper
argmap) (fn [_] {:plushy (genome/make-random-plushy instructions max-initial-plushy-size)})
indexed-training-data (if downsample? (range population-size))] ;creates population of random plushys
(downsample/assign-indices-to-data (downsample/initialize-case-distances argmap) argmap) (let [evaluated-pop (sort-by :total-error
(:training-data argmap))] (mapper
(let [training-data (if downsample? (partial error-function argmap (:training-data argmap))
(case (:ds-function argmap) population)) ;population sorted by :total-error
:case-maxmin (downsample/select-downsample-maxmin indexed-training-data argmap) best-individual (first evaluated-pop)
:case-maxmin-auto (downsample/select-downsample-maxmin-adaptive indexed-training-data argmap) argmap (if (= (:parent-selection argmap) :epsilon-lexicase)
:case-rand (downsample/select-downsample-random indexed-training-data argmap) (assoc argmap :epsilons (selection/epsilon-list evaluated-pop))
(do (prn {:error "Invalid Downsample Function"}) argmap)] ;adds :epsilons if using epsilon-lexicase
(downsample/select-downsample-random indexed-training-data argmap))) (if (:custom-report argmap)
indexed-training-data) ;defaults to full training set ((:custom-report argmap) evaluated-pop generation argmap)
parent-reps (if (report evaluated-pop generation argmap))
(and downsample? ; if we are down-sampling (cond
(zero? (mod generation ds-parent-gens))) ;every ds-parent-gens generations ;; Success on training cases is verified on testing cases
(take (* ds-parent-rate (count population)) (shuffle population)) (<= (:total-error best-individual) solution-error-threshold)
'()) ;else just empty list (do (prn {:success-generation generation})
; parent representatives for down-sampling (prn {:total-test-error
rep-evaluated-pop (if downsample? (:total-error (error-function argmap (:testing-data argmap) best-individual))})
(sort-by :total-error population-sort-comp (when (:simplification? argmap)
(utils/pmapallv (let [simplified-plushy (simplification/auto-simplify-plushy (:plushy best-individual) error-function argmap)]
(partial error-function argmap indexed-training-data) (prn {:total-test-error-simplified (:total-error (error-function argmap (:testing-data argmap) (hash-map :plushy simplified-plushy)))}))))
parent-reps ;;
argmap)) (>= generation max-generations)
'()) nil
evaluated-pop (sort-by :total-error population-sort-comp ;;
(utils/pmapallv :else (recur (inc generation)
(partial error-function argmap training-data) (if (:elitism argmap)
population (conj (repeatedly (dec population-size)
argmap)) #(variation/new-individual evaluated-pop argmap))
best-individual (first evaluated-pop) (first evaluated-pop)) ;elitism maintains the most-fit individual
best-individual-passes-ds (and downsample? (<= (:total-error best-individual) solution-error-threshold)) (repeatedly population-size
argmap (if (= (:parent-selection argmap) :epsilon-lexicase) #(variation/new-individual evaluated-pop argmap))))))))
(assoc argmap :epsilons (selection/epsilon-list evaluated-pop))
argmap)] ; epsilons
(if (:custom-report argmap)
((:custom-report argmap) evaluations evaluated-pop generation argmap)
(report evaluations evaluated-pop generation argmap training-data))
;; Did the indvidual pass all cases in ds?
(when best-individual-passes-ds
(prn {:semi-success-generation generation}))
(cond
;; If either the best individual on the ds passes all training cases, or best individual on full
;; sample passes all training cases, we verify success on test cases and exit, succeeding
(if (or (and best-individual-passes-ds
(<= (:total-error (error-function argmap indexed-training-data best-individual))
solution-error-threshold))
(and (not downsample?)
(<= (:total-error best-individual)
solution-error-threshold)))
(do (prn {:success-generation generation})
(prn {:successful-plushy (:plushy best-individual)})
(prn {:successful-program (genome/plushy->push (:plushy best-individual) argmap)})
(prn {:total-test-error
(:total-error (error-function argmap (:testing-data argmap) best-individual))})
(when (:simplification? argmap)
(let [simplified-plushy (simplification/auto-simplify-plushy (:plushy best-individual) error-function argmap)]
(prn {:total-test-error-simplified
(:total-error (error-function argmap (:testing-data argmap) {:plushy simplified-plushy}))})
(prn {:simplified-plushy simplified-plushy})
(prn {:simplified-program (genome/plushy->push simplified-plushy argmap)})))
(if dont-end false true))
false)
(cleanup)
;; If we've evolved for as many generations as the parameters allow, exit without succeeding
(or (and (not downsample?)
(>= generation max-generations))
(and downsample?
(>= evaluations (* max-generations population-size (count indexed-training-data)))))
(cleanup)
;; Otherwise, evolve for another generation
: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
(if (:elitism argmap) ; elitism maintains the individual with lowest total error
(conj (utils/pmapallv (fn [_] (variation/new-individual evaluated-pop argmap))
(range (dec population-size))
argmap)
(first evaluated-pop))
(utils/pmapallv (fn [_] (variation/new-individual evaluated-pop argmap))
(range population-size)
argmap))
(if downsample?
(if (zero? (mod generation ds-parent-gens))
; update distances every ds-parent-gens generations
(downsample/update-case-distances rep-evaluated-pop
indexed-training-data
indexed-training-data
ids-type
(/ solution-error-threshold
(count indexed-training-data)))
indexed-training-data)
indexed-training-data)))))))

View File

@ -2,6 +2,7 @@
(:require [psb2.core :as psb2] (:require [psb2.core :as psb2]
[propeller.genome :as genome] [propeller.genome :as genome]
[propeller.push.interpreter :as interpreter] [propeller.push.interpreter :as interpreter]
[propeller.problems.data-creation :as dc]
[propeller.utils :as utils] [propeller.utils :as utils]
[propeller.push.instructions :refer [get-stack-instructions]] [propeller.push.instructions :refer [get-stack-instructions]]
[propeller.push.state :as state] [propeller.push.state :as state]
@ -55,9 +56,7 @@
:cljs (apply + errors))))) :cljs (apply + errors)))))
(defn -main (defn -main
"Runs the top-level genetic programming function, giving it a map of "Runs propel-gp, giving it a map of arguments."
arguments with defaults that can be overridden from the command line
or through a passed map."
[& args] [& args]
(gp/gp (gp/gp
(merge (merge
@ -65,9 +64,6 @@
:error-function error-function :error-function error-function
:training-data train-data :training-data train-data
:testing-data test-data :testing-data test-data
:case-t-size (count train-data)
:ds-parent-rate 0
:ds-parent-gens 1
:max-generations 300 :max-generations 300
:population-size 1000 :population-size 1000
:max-initial-plushy-size 250 :max-initial-plushy-size 250
@ -77,4 +73,5 @@
:umad-rate 0.1 :umad-rate 0.1
:variation {:umad 1.0 :crossover 0.0} :variation {:umad 1.0 :crossover 0.0}
:elitism false} :elitism false}
(apply hash-map (map #(if (string? %) (read-string %) %) args))))) (apply hash-map (map #(if (string? %) (read-string %) %) args))))
(#?(:clj shutdown-agents)))

View File

@ -3,6 +3,7 @@
[psb2.core :as psb2] [psb2.core :as psb2]
[propeller.genome :as genome] [propeller.genome :as genome]
[propeller.push.interpreter :as interpreter] [propeller.push.interpreter :as interpreter]
[propeller.problems.data-creation :as dc]
[propeller.push.state :as state] [propeller.push.state :as state]
[propeller.push.instructions :refer [get-stack-instructions]] [propeller.push.instructions :refer [get-stack-instructions]]
[propeller.utils :as utils] [propeller.utils :as utils]
@ -73,9 +74,7 @@
:cljs (apply + errors))))) :cljs (apply + errors)))))
(defn -main (defn -main
"Runs the top-level genetic programming function, giving it a map of "Runs propel-gp, giving it a map of arguments."
arguments with defaults that can be overridden from the command line
or through a passed map."
[& args] [& args]
(gp/gp (gp/gp
(merge (merge
@ -83,9 +82,6 @@
:error-function error-function :error-function error-function
:training-data train-data :training-data train-data
:testing-data test-data :testing-data test-data
:case-t-size (count train-data)
:ds-parent-rate 0
:ds-parent-gens 1
:max-generations 300 :max-generations 300
:population-size 1000 :population-size 1000
:max-initial-plushy-size 250 :max-initial-plushy-size 250

View File

@ -4,6 +4,7 @@
[propeller.push.interpreter :as interpreter] [propeller.push.interpreter :as interpreter]
[clojure.string :as string] [clojure.string :as string]
[propeller.tools.math :as math] [propeller.tools.math :as math]
[propeller.problems.data-creation :as dc]
[propeller.utils :as utils] [propeller.utils :as utils]
[propeller.push.instructions :refer [get-stack-instructions]] [propeller.push.instructions :refer [get-stack-instructions]]
[propeller.push.state :as state] [propeller.push.state :as state]
@ -124,9 +125,7 @@
:cljs (apply + errors))))) :cljs (apply + errors)))))
(defn -main (defn -main
"Runs the top-level genetic programming function, giving it a map of "Runs propel-gp, giving it a map of arguments."
arguments with defaults that can be overridden from the command line
or through a passed map."
[& args] [& args]
(gp/gp (gp/gp
(merge (merge
@ -134,9 +133,6 @@
:error-function error-function :error-function error-function
:training-data train-data :training-data train-data
:testing-data test-data :testing-data test-data
:case-t-size (count train-data)
:ds-parent-rate 0
:ds-parent-gens 1
:max-generations 300 :max-generations 300
:population-size 1000 :population-size 1000
:max-initial-plushy-size 250 :max-initial-plushy-size 250
@ -146,4 +142,5 @@
:umad-rate 0.1 :umad-rate 0.1
:variation {:umad 1.0 :crossover 0.0} :variation {:umad 1.0 :crossover 0.0}
:elitism false} :elitism false}
(apply hash-map (map #(if (string? %) (read-string %) %) args))))) (apply hash-map (map #(if (string? %) (read-string %) %) args))))
(#?(:clj shutdown-agents)))

View File

@ -2,6 +2,7 @@
(:require [psb2.core :as psb2] (:require [psb2.core :as psb2]
[propeller.genome :as genome] [propeller.genome :as genome]
[propeller.push.interpreter :as interpreter] [propeller.push.interpreter :as interpreter]
[propeller.problems.data-creation :as dc]
[propeller.utils :as utils] [propeller.utils :as utils]
[propeller.push.instructions :refer [get-stack-instructions]] [propeller.push.instructions :refer [get-stack-instructions]]
[propeller.push.state :as state] [propeller.push.state :as state]
@ -56,9 +57,7 @@
(defn -main (defn -main
"Runs the top-level genetic programming function, giving it a map of "Runs propel-gp, giving it a map of arguments."
arguments with defaults that can be overridden from the command line
or through a passed map."
[& args] [& args]
(gp/gp (gp/gp
(merge (merge
@ -66,9 +65,6 @@
:error-function error-function :error-function error-function
:training-data train-data :training-data train-data
:testing-data test-data :testing-data test-data
:case-t-size (count train-data)
:ds-parent-rate 0
:ds-parent-gens 1
:max-generations 300 :max-generations 300
:population-size 1000 :population-size 1000
:max-initial-plushy-size 250 :max-initial-plushy-size 250
@ -78,4 +74,5 @@
:umad-rate 0.1 :umad-rate 0.1
:variation {:umad 1.0 :crossover 0.0} :variation {:umad 1.0 :crossover 0.0}
:elitism false} :elitism false}
(apply hash-map (map #(if (string? %) (read-string %) %) args))))) (apply hash-map (map #(if (string? %) (read-string %) %) args))))
(#?(:clj shutdown-agents)))

View File

@ -66,9 +66,7 @@
:cljs (apply + errors))))) :cljs (apply + errors)))))
(defn -main (defn -main
"Runs the top-level genetic programming function, giving it a map of "Runs propel-gp, giving it a map of arguments."
arguments with defaults that can be overridden from the command line
or through a passed map."
[& args] [& args]
(gp/gp (gp/gp
(merge (merge
@ -85,4 +83,5 @@
:umad-rate 0.1 :umad-rate 0.1
:variation {:umad 1.0 :crossover 0.0} :variation {:umad 1.0 :crossover 0.0}
:elitism false} :elitism false}
(apply hash-map (map #(if (string? %) (read-string %) %) args))))) (apply hash-map (map #(if (string? %) (read-string %) %) args))))
(#?(:clj shutdown-agents)))

View File

@ -77,9 +77,7 @@ Source: https://arxiv.org/pdf/2106.06086.pdf"
:cljs (apply + errors))))) :cljs (apply + errors)))))
(defn -main (defn -main
"Runs the top-level genetic programming function, giving it a map of "Runs propel-gp, giving it a map of arguments."
arguments with defaults that can be overridden from the command line
or through a passed map."
[& args] [& args]
(gp/gp (gp/gp
(merge (merge
@ -96,4 +94,5 @@ Source: https://arxiv.org/pdf/2106.06086.pdf"
:umad-rate 0.1 :umad-rate 0.1
:variation {:umad 1.0 :crossover 0.0} :variation {:umad 1.0 :crossover 0.0}
:elitism false} :elitism false}
(apply hash-map (map #(if (string? %) (read-string %) %) args))))) (apply hash-map (map #(if (string? %) (read-string %) %) args))))
(#?(:clj shutdown-agents)))

View File

@ -66,9 +66,7 @@ Source: https://arxiv.org/pdf/2106.06086.pdf"
:cljs (apply + errors))))) :cljs (apply + errors)))))
(defn -main (defn -main
"Runs the top-level genetic programming function, giving it a map of "Runs propel-gp, giving it a map of arguments."
arguments with defaults that can be overridden from the command line
or through a passed map."
[& args] [& args]
(gp/gp (gp/gp
(merge (merge
@ -85,4 +83,5 @@ Source: https://arxiv.org/pdf/2106.06086.pdf"
:umad-rate 0.1 :umad-rate 0.1
:variation {:umad 1.0 :crossover 0.0} :variation {:umad 1.0 :crossover 0.0}
:elitism false} :elitism false}
(apply hash-map (map #(if (string? %) (read-string %) %) args))))) (apply hash-map (map #(if (string? %) (read-string %) %) args))))
(#?(:clj shutdown-agents)))

View File

@ -100,9 +100,7 @@ Source: https://arxiv.org/pdf/2106.06086.pdf"
:cljs (apply + errors))))) :cljs (apply + errors)))))
(defn -main (defn -main
"Runs the top-level genetic programming function, giving it a map of "Runs propel-gp, giving it a map of arguments."
arguments with defaults that can be overridden from the command line
or through a passed map."
[& args] [& args]
(gp/gp (gp/gp
(merge (merge
@ -119,4 +117,5 @@ Source: https://arxiv.org/pdf/2106.06086.pdf"
:umad-rate 0.1 :umad-rate 0.1
:variation {:umad 1.0 :crossover 0.0} :variation {:umad 1.0 :crossover 0.0}
:elitism false} :elitism false}
(apply hash-map (map #(if (string? %) (read-string %) %) args))))) (apply hash-map (map #(if (string? %) (read-string %) %) args))))
(#?(:clj shutdown-agents)))

View File

@ -74,9 +74,7 @@ Source: https://arxiv.org/pdf/2106.06086.pdf"
:cljs (apply + errors))))) :cljs (apply + errors)))))
(defn -main (defn -main
"Runs the top-level genetic programming function, giving it a map of "Runs propel-gp, giving it a map of arguments."
arguments with defaults that can be overridden from the command line
or through a passed map."
[& args] [& args]
(gp/gp (gp/gp
(merge (merge
@ -93,4 +91,5 @@ Source: https://arxiv.org/pdf/2106.06086.pdf"
:umad-rate 0.1 :umad-rate 0.1
:variation {:umad 1.0 :crossover 0.0} :variation {:umad 1.0 :crossover 0.0}
:elitism false} :elitism false}
(apply hash-map (map #(if (string? %) (read-string %) %) args))))) (apply hash-map (map #(if (string? %) (read-string %) %) args))))
(#?(:clj shutdown-agents)))

View File

@ -1,107 +0,0 @@
(ns propeller.problems.PSB2.find-pair
(:require [psb2.core :as psb2]
[propeller.genome :as genome]
[propeller.push.interpreter :as interpreter]
[propeller.utils :as utils]
[propeller.push.instructions :refer [def-instruction get-stack-instructions]]
[propeller.push.state :as state]
[propeller.tools.math :as math]
[propeller.gp :as gp]
#?(:cljs [cljs.reader :refer [read-string]])))
(def train-and-test-data (psb2/fetch-examples "data" "find-pair" 200 2000))
(def train-data (:train train-and-test-data))
(def test-data (:test train-and-test-data))
(defn random-int [] (- (rand-int 201) 100))
(defn map-vals-input
"Returns all the input values of a map"
[i]
(vals (select-keys i [:input1 :input2])))
(defn map-vals-output
"Returns the output values of a map"
[i]
(vals (select-keys i [:output1 :output2])))
(def-instruction :output-one
^{:stacks #{:integer :output}}
(fn [state]
(if (empty? (:integer state))
state
(let [top-int (state/peek-stack state :integer)]
(assoc-in state [:output :out1] top-int)))))
(def-instruction :output-two
^{:stacks #{:integer :output}}
(fn [state]
(if (empty? (:integer state))
state
(let [top-int (state/peek-stack state :integer)]
(assoc-in state [:output :out2] top-int)))))
(def instructions
(utils/not-lazy
(concat
;;; stack-specific instructions
(get-stack-instructions #{:exec :integer :vector_integer :boolean})
(list :output-one :output-two)
;;; input instructions
(list :in1 :in2)
;;; close
(list 'close)
;;; ERCs (constants)
(list -1 0 1 2 random-int))))
(defn error-function
[argmap data individual]
(let [program (genome/plushy->push (:plushy individual) argmap)
inputs (map (fn [i] (map-vals-input i)) data)
correct-outputs (map (fn [i] (map-vals-output i)) data)
outputs (map (fn [input]
(:output
(interpreter/interpret-program
program
(assoc state/empty-state :input {:in1 (nth input 0)
:in2 (nth input 1)})
(:step-limit argmap))))
inputs)
outputs-1 (map #(:out1 %) outputs)
outputs-2 (map #(:out2 %) outputs)
;_ (prn {:o1 outputs-1 :o2 outputs-2})
errors (map (fn [correct-output output-1 output-2]
(if (not (and (number? output-2) (number? output-1)))
100000
(+ (math/abs (- (first correct-output) output-1))
(math/abs (- (second correct-output) output-2)))))
correct-outputs outputs-1 outputs-2)]
(assoc individual
:behaviors outputs
:errors errors
:total-error #?(:clj (apply +' errors)
:cljs (apply + errors)))))
(defn -main
"Runs propel-gp, giving it a map of arguments."
[& args]
(gp/gp
(merge
{:instructions instructions
:error-function error-function
:training-data train-data
:testing-data test-data
:case-t-size (count train-data)
:ds-parent-rate 0
:ds-parent-gens 1
:max-generations 300
:population-size 1000
:max-initial-plushy-size 250
:step-limit 2000
:parent-selection :lexicase
:tournament-size 5
:umad-rate 0.1
:variation {:umad 1.0 :crossover 0.0}
:elitism false}
(apply hash-map (map #(if (string? %) (read-string %) %) args))))
(#?(:clj shutdown-agents)))

View File

@ -17,21 +17,7 @@ Source: https://arxiv.org/pdf/2106.06086.pdf"
[propeller.gp :as gp] [propeller.gp :as gp]
#?(:cljs [cljs.reader :refer [read-string]]))) #?(:cljs [cljs.reader :refer [read-string]])))
; =========== PROBLEM DESCRIPTION =========================
; FIZZ BUZZ from PSB2
; Given an integer x, return "Fizz" if x is
; divisible by 3, "Buzz" if x is divisible by 5, "FizzBuzz" if x
; is divisible by 3 and 5, and a string version of x if none of
; the above hold.
;
; Source: https://arxiv.org/pdf/2106.06086.pdf
; ============================================================
(def train-and-test-data "Data taken from https://zenodo.org/record/5084812" (psb2/fetch-examples "data" "fizz-buzz" 200 2000)) (def train-and-test-data "Data taken from https://zenodo.org/record/5084812" (psb2/fetch-examples "data" "fizz-buzz" 200 2000))
(def train-data (:train train-and-test-data))
(def test-data (:test train-and-test-data))
(def instructions (def instructions
"Stack-specific instructions, input instructions, close, and constants" "Stack-specific instructions, input instructions, close, and constants"
@ -77,19 +63,14 @@ Source: https://arxiv.org/pdf/2106.06086.pdf"
(defn -main (defn -main
"Runs the top-level genetic programming function, giving it a map of "Runs propel-gp, giving it a map of arguments."
arguments with defaults that can be overridden from the command line
or through a passed map."
[& args] [& args]
(gp/gp (gp/gp
(merge (merge
{:instructions instructions {:instructions instructions
:error-function error-function :error-function error-function
:training-data train-data :training-data (:train train-and-test-data)
:testing-data test-data :testing-data (:test train-and-test-data)
:case-t-size (count train-data)
:ds-parent-rate 0
:ds-parent-gens 1
:max-generations 300 :max-generations 300
:population-size 1000 :population-size 1000
:max-initial-plushy-size 250 :max-initial-plushy-size 250
@ -99,4 +80,5 @@ Source: https://arxiv.org/pdf/2106.06086.pdf"
:umad-rate 0.1 :umad-rate 0.1
:variation {:umad 1.0 :crossover 0.0} :variation {:umad 1.0 :crossover 0.0}
:elitism false} :elitism false}
(apply hash-map (map #(if (string? %) (read-string %) %) args))))) (apply hash-map (map #(if (string? %) (read-string %) %) args))))
(#?(:clj shutdown-agents)))

View File

@ -18,19 +18,7 @@ Source: https://arxiv.org/pdf/2106.06086.pdf"
[propeller.gp :as gp] [propeller.gp :as gp]
#?(:cljs [cljs.reader :refer [read-string]]))) #?(:cljs [cljs.reader :refer [read-string]])))
; =========== PROBLEM DESCRIPTION =========================
; FUEL COST from PSB2
; Given a vector of positive integers, divide
; each by 3, round the result down to the nearest integer, and
; subtract 2. Return the sum of all of the new integers in the
; vector
;
; Source: https://arxiv.org/pdf/2106.06086.pdf
; ============================================================
(def train-and-test-data "Data taken from https://zenodo.org/record/5084812" (psb2/fetch-examples "data" "fuel-cost" 200 2000)) (def train-and-test-data "Data taken from https://zenodo.org/record/5084812" (psb2/fetch-examples "data" "fuel-cost" 200 2000))
(def train-data (:train train-and-test-data))
(def test-data (:test train-and-test-data))
; Random integer between -100 and 100 (from smallest) ; Random integer between -100 and 100 (from smallest)
(defn random-int "Random integer between -100 and 100" [] (- (rand-int 201) 100)) (defn random-int "Random integer between -100 and 100" [] (- (rand-int 201) 100))
@ -38,15 +26,15 @@ Source: https://arxiv.org/pdf/2106.06086.pdf"
(def instructions (def instructions
"Stack-specific instructions, input instructions, close, and constants" "Stack-specific instructions, input instructions, close, and constants"
(utils/not-lazy (utils/not-lazy
(concat (concat
;;; stack-specific instructions ;;; stack-specific instructions
(get-stack-instructions #{:exec :integer :boolean :vector_integer :print}) (get-stack-instructions #{:exec :integer :boolean :vector_integer :print})
;;; input instructions ;;; input instructions
(list :in1) (list :in1)
;;; close ;;; close
(list 'close) (list 'close)
;;; ERCs (constants) ;;; ERCs (constants)
(list random-int 0 1 2 3)))) (list random-int 0 1 2 3))))
(defn error-function (defn error-function
"Finds the behaviors and errors of an individual: Error is 0 if the value and "Finds the behaviors and errors of an individual: Error is 0 if the value and
@ -59,11 +47,11 @@ Source: https://arxiv.org/pdf/2106.06086.pdf"
correct-outputs (map (fn [i] (get i :output1)) data) correct-outputs (map (fn [i] (get i :output1)) data)
outputs (map (fn [input] outputs (map (fn [input]
(state/peek-stack (state/peek-stack
(interpreter/interpret-program (interpreter/interpret-program
program program
(assoc state/empty-state :input {:in1 input}) (assoc state/empty-state :input {:in1 input})
(:step-limit argmap)) (:step-limit argmap))
:integer)) :integer))
inputs) inputs)
errors (map (fn [correct-output output] errors (map (fn [correct-output output]
(if (= output :no-stack-item) (if (= output :no-stack-item)
@ -72,32 +60,28 @@ Source: https://arxiv.org/pdf/2106.06086.pdf"
correct-outputs correct-outputs
outputs)] outputs)]
(assoc individual (assoc individual
:behaviors outputs :behaviors outputs
:errors errors :errors errors
:total-error #?(:clj (apply +' errors) :total-error #?(:clj (apply +' errors)
:cljs (apply + errors))))) :cljs (apply + errors)))))
(defn -main (defn -main
"Runs the top-level genetic programming function, giving it a map of "Runs propel-gp, giving it a map of arguments."
arguments with defaults that can be overridden from the command line
or through a passed map."
[& args] [& args]
(gp/gp (gp/gp
(merge (merge
{:instructions instructions {:instructions instructions
:error-function error-function :error-function error-function
:training-data train-data :training-data (:train train-and-test-data)
:testing-data test-data :testing-data (:test train-and-test-data)
:case-t-size (count train-data) :max-generations 300
:ds-parent-rate 0 :population-size 1000
:ds-parent-gens 1 :max-initial-plushy-size 250
:max-generations 300 :step-limit 2000
:population-size 1000 :parent-selection :lexicase
:max-initial-plushy-size 250 :tournament-size 5
:step-limit 2000 :umad-rate 0.1
:parent-selection :lexicase :variation {:umad 1.0 :crossover 0.0}
:tournament-size 5 :elitism false}
:umad-rate 0.1 (apply hash-map (map #(if (string? %) (read-string %) %) args))))
:variation {:umad 1.0 :crossover 0.0} (#?(:clj shutdown-agents)))
:elitism false}
(apply hash-map (map #(if (string? %) (read-string %) %) args)))))

View File

@ -18,11 +18,6 @@ Source: https://arxiv.org/pdf/2106.06086.pdf"
(def train-and-test-data "Data taken from https://zenodo.org/record/5084812" (psb2/fetch-examples "data" "gcd" 200 2000)) (def train-and-test-data "Data taken from https://zenodo.org/record/5084812" (psb2/fetch-examples "data" "gcd" 200 2000))
(def train-and-test-data (psb2/fetch-examples "data" "gcd" 200 2000))
(def train-data (:train train-and-test-data))
(def test-data (:test train-and-test-data))
(defn random-int "Random integer between -100 and 100" [] (- (rand-int 201) 100)) (defn random-int "Random integer between -100 and 100" [] (- (rand-int 201) 100))
(defn map-vals-input (defn map-vals-input
@ -79,19 +74,14 @@ Source: https://arxiv.org/pdf/2106.06086.pdf"
:cljs (apply + errors))))) :cljs (apply + errors)))))
(defn -main (defn -main
"Runs the top-level genetic programming function, giving it a map of "Runs propel-gp, giving it a map of arguments."
arguments with defaults that can be overridden from the command line
or through a passed map."
[& args] [& args]
(gp/gp (gp/gp
(merge (merge
{:instructions instructions {:instructions instructions
:error-function error-function :error-function error-function
:training-data train-data :training-data (:train train-and-test-data)
:testing-data test-data :testing-data (:test train-and-test-data)
:case-t-size (count train-data)
:ds-parent-rate 0
:ds-parent-gens 1
:max-generations 300 :max-generations 300
:population-size 1000 :population-size 1000
:max-initial-plushy-size 250 :max-initial-plushy-size 250
@ -101,4 +91,5 @@ Source: https://arxiv.org/pdf/2106.06086.pdf"
:umad-rate 0.1 :umad-rate 0.1
:variation {:umad 1.0 :crossover 0.0} :variation {:umad 1.0 :crossover 0.0}
:elitism false} :elitism false}
(apply hash-map (map #(if (string? %) (read-string %) %) args))))) (apply hash-map (map #(if (string? %) (read-string %) %) args))))
(#?(:clj shutdown-agents)))

View File

@ -68,9 +68,7 @@ Source: https://arxiv.org/pdf/2106.06086.pdf"
:cljs (apply + errors))))) :cljs (apply + errors)))))
(defn -main (defn -main
"Runs the top-level genetic programming function, giving it a map of "Runs propel-gp, giving it a map of arguments."
arguments with defaults that can be overridden from the command line
or through a passed map."
[& args] [& args]
(gp/gp (gp/gp
(merge (merge
@ -87,4 +85,5 @@ Source: https://arxiv.org/pdf/2106.06086.pdf"
:umad-rate 0.1 :umad-rate 0.1
:variation {:umad 1.0 :crossover 0.0} :variation {:umad 1.0 :crossover 0.0}
:elitism false} :elitism false}
(apply hash-map (map #(if (string? %) (read-string %) %) args))))) (apply hash-map (map #(if (string? %) (read-string %) %) args))))
(#?(:clj shutdown-agents)))

View File

@ -64,9 +64,7 @@ Source: https://arxiv.org/pdf/2106.06086.pdf"
:cljs (apply + errors))))) :cljs (apply + errors)))))
(defn -main (defn -main
"Runs the top-level genetic programming function, giving it a map of "Runs propel-gp, giving it a map of arguments."
arguments with defaults that can be overridden from the command line
or through a passed map."
[& args] [& args]
(gp/gp (gp/gp
(merge (merge
@ -83,4 +81,5 @@ Source: https://arxiv.org/pdf/2106.06086.pdf"
:umad-rate 0.1 :umad-rate 0.1
:variation {:umad 1.0 :crossover 0.0} :variation {:umad 1.0 :crossover 0.0}
:elitism false} :elitism false}
(apply hash-map (map #(if (string? %) (read-string %) %) args))))) (apply hash-map (map #(if (string? %) (read-string %) %) args))))
(#?(:clj shutdown-agents)))

Some files were not shown because too many files have changed in this diff Show More