https://github.com/saulshanabrook/search-in-clojure
metaheuristic search in Clojure
https://github.com/saulshanabrook/search-in-clojure
Last synced: 8 months ago
JSON representation
metaheuristic search in Clojure
- Host: GitHub
- URL: https://github.com/saulshanabrook/search-in-clojure
- Owner: saulshanabrook
- License: mit
- Created: 2016-01-25T04:47:15.000Z (over 9 years ago)
- Default Branch: master
- Last Pushed: 2016-08-02T02:37:18.000Z (about 9 years ago)
- Last Synced: 2025-02-15T08:17:05.490Z (8 months ago)
- Language: Clojure
- Homepage:
- Size: 2.21 MB
- Stars: 1
- Watchers: 2
- Forks: 0
- Open Issues: 15
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
# search
[](https://travis-ci.org/saulshanabrook/search-in-clojure) [](http://saulshanabrook.github.io/search-in-clojure/)
A Clojure framework for [metaheauristc search algorithms](https://en.wikipedia.org/wiki/Metaheuristic).
## Why?
This framework is built to help with the research at the
[Hampshire College Institute for Computational Intelligence](http://faculty.hampshire.edu/lspector/ici.html)
into genetic programming using the [Push programming language](http://faculty.hampshire.edu/lspector/push.html).The current framework ([Clojush](https://github.com/lspector/Clojush)) makes
heavy use of global state and does not clearly separate the Push programming
language from the evolutionary search code. Combined with a lack of unit tests,
this made it very hard to implement large scale additions without an intricate
knowledge of how everything connected.In contrast, this framework has a small core ([`search.core`](./src/search/core.clj))
and implements all the interesting parts of search as pluggable modules.## Runs
*We make heavy use of plumatic's [schema](https://github.com/plumatic/schema/)
and [plumbing](https://github.com/plumatic/plumbing/) libraries. Schema gives
us some nice (optional) runtime type validation as well as making the codebase
more explicit. We use the plumbing library to do all of our computation with
it's graph module.*A `Run` is one attempt to solve a problem. You can also think of this as an "experiment"
or a "search". For example, it could be trying to solve a specific SR problem
with push. It is created from a `Config`.Regardless of your algorithm or problem, your `Run` will have a `:generations`
key that is a sequence of [`search.core/Generation`](./src/search/core.clj)s. The
`search.core/config->run` function is really the only core logic in
this library. The command line tool is just a small wrapper around this.In between making a `Run`, from a `Config`, we create a graph.
```
Config -> some graph -> Run
```Once we have this graph we just run it with no arguments to generate the `Run`.
So the `Config` specifies how to create the graph.The whole point of the `Config` is to create a graph with a `generations` key.
At a minimum, it contains a list of graphs to merge together. For example,
we can combine a symbolic regression in push problem with a genetic algorithm:```bash
lein trampoline run -g '[search.graphs.problems.push-sr/plus-six-graph search.graphs.algorithms.genetic/graph]'
```The `-g` here stands for `graph-symbols`.
When these two graphs are merged together, then they have all the
pieces needed to be able to be run to get the `generations` key.This isn't very interesting so far, however, because it is entirely side effect
free (so we don't see any output). Let's begin by printing out the individual
with the smallest sum of errors, during each generation.```bash
lein trampoline run -g '[search.graphs.problems.push-sr/plus-six-graph search.graphs.algorithms.genetic/graph]' \
-w '[(partial search.wrappers.recorders/wrap search.wrappers.recorders/smallest-ind)]'
```Here the `-w` means `wrapper-forms`. This is a list of forms, that when evaluated,
should each return a function that wraps the whole graph.At least when I run this, it isn't very interesting because it solves it in
the first generation. Let's limit the population-size so it takes a bit longer.```bash
lein trampoline run -g '[search.graphs.problems.push-sr/plus-six-graph search.graphs.algorithms.genetic/graph]' \
-w '[(partial search.wrappers.recorders/wrap search.wrappers.recorders/smallest-ind)]' \
-v '{:population-size 50}'
```We can also add profiling:
```bash
lein trampoline run -g '[search.graphs.problems.push-sr/plus-six-graph search.graphs.algorithms.genetic/graph]' \
-w '[(partial search.wrappers.recorders/wrap search.wrappers.recorders/smallest-ind) search.wrappers.graph/profile-fns-wrap search.wrappers.graph/print-profile-gen-wrap]' \
-v '{:population-size 50}'
```The `perf` profile in lein enables some optimizations and can be enabled
with `lein with-profile +perf trampoline run ...`Also, if you are getting an error in the run, you can add the `-s` flag to
enable schema validation, which should help you verify and diagnose the
execution. It does have some (untested) performance penalty so it is disabled
by default.### Execution
To run the evaluations in parallel, you can set a custom `map` function, by
changing the `map-fn` key:```bash
lein run ... -v '{:map-fn (partial com.climate.claypoole/pmap :builtin)}'
```## Contributing
The code is split into two main subdirectory, `graphs` and `wrappers`.
Primarily, the difference is that the `graphs` provides a bunch of, well, graphs
that get merged together. At the top level, these are the `algorithms` and
`problems`. Each of those in tern combines a bunch of graphs and together make
up a full search. This is just how it has worked out so far, however. It is
totally possible to design a whole search without using any of the existing
graphs, you would just end up repeating a lot of things.The `wrappers` are all function that take a graph as input and return a new
graph. They do fancy things like add a stateful function call as the generations
are evaluated and add profiling.