Skip to content

Commit

Permalink
Merge branch 'release/v0.7.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
markcoletti committed Aug 6, 2021
2 parents aafada0 + fcb2d9b commit 01c5473
Show file tree
Hide file tree
Showing 45 changed files with 1,340 additions and 527 deletions.
5 changes: 5 additions & 0 deletions ACKNOWLEDGEMENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# ACKNOWLEDGEMENTS

## Contributors

Thomas Hopkins, Rensselaer Polytechnic Institute
51 changes: 35 additions & 16 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,26 @@

Being a terse compilation by version of changes.

## 0.7.0
## 0.8.0dev (current develop branch)

## 0.7.0, 8/5/2021

* New features
* Added `ops.sus_selection()` and `ops.proportional_selection()`

* API changes
* Made `numpy` arrays (instead of lists) the default representation for most LEAP operators and examples, for a significant speedup.
* Added `indices` parameter to `ops.random_selection()`
* `plot_2d_problem()` now defaults to checking the `problem.bounds` field for `xlim` and `ylim` values
* `ea_solve()` now accepts optional Dask `Client` object to enable
parallel evaluations
* `generational_ea()` now supports elitism by default


## 0.6.0, 6/13/2021

* Drop support for Python 3.6
* This keeps us in sync with numpy and dask that also dropped support for 3.6 this year
* This keeps us in sync with `numpy` and `dask`, which also dropped support for 3.6 this year

* New features
* Added `landscape_features` package with some initial exploratory landscape analysis tools
Expand All @@ -19,7 +33,7 @@ Being a terse compilation by version of changes.
* Added support for lexicographical and Koza-style parsimony pressure
* Added `HistPhenotypePlotProbe`
* Added `ops.grouped_evaluate()` for evaluating batches of individuals
* Added `ExternalProcessproblem` for using external programs as fitness functions
* Added `ExternalProcessProblem` for using external programs as fitness functions

* Documentation
* Added documentation on `leap_ec.context` and updated software development
Expand All @@ -33,10 +47,10 @@ Being a terse compilation by version of changes.
* Improved test coverage

* Bugfixes
* Fix `viz` parameter when calling `simple.ea_solve()`
* Fix algebra error in `real_rep.problems.NoisyQuarticProblem`
* Tell `dask` that functions are impure by default, to make sure it doesn't cache results
* Change `Makefile` to use `pip install -e .` instead of the deprecated `python setup.py develop`
* Fixed `viz` parameter when calling `simple.ea_solve()`
* Fixed algebra error in `real_rep.problems.NoisyQuarticProblem`
* Told `dask` that functions are impure by default, to make sure it doesn't cache results
* Changed `Makefile` to use `pip install -e .` instead of the deprecated `python setup.py develop`

* API changes
* Significantly refactored the `executable_rep.rules` package to simplify learning classifier systems
Expand Down Expand Up @@ -69,15 +83,20 @@ Being a terse compilation by version of changes.

## 0.5.0, 1/9/2021

* Added probability parameter for the `n_ary_crossover` operator
* Greatly improved test coverage
* Added support for static- and variable-length segments, which are fixed-length "chunks" of values
* Added a simple neural network representation, `executable_rep.neural_network`, and made it the default for `examples/openai_gym.py`
* Changed the `Executable` interface to act as a `Callable` object (rather than using a custom `output()` method)
* Added `statistical_helpers` to assist with writing unit tests for stochastic algorithms
* Added support for integer representations, via the `int_rep` package
* Added a Cartesian genetic programming (CGP) representation, `executable_rep.cgp`, with example in `examples/cgp.py`
* Added support for heterogeneous island models, demoed in `examples/multitask_island_model.py`
* New features
* Added support for static- and variable-length segments, which are fixed-length "chunks" of values
* Added support for integer representations, via the `int_rep` package
* Added a simple neural network representation, `executable_rep.neural_network`, and made it the default for `examples/openai_gym.py`
* Added a Cartesian genetic programming (CGP) representation, `executable_rep.cgp`, with example in `examples/cgp.py`
* Added support for heterogeneous island models, demoed in `examples/multitask_island_model.py`

* CI/CD
* Greatly improved test coverage
* Added `statistical_helpers` to assist with writing unit tests for stochastic algorithms

* API changes
* Added probability parameter for the `n_ary_crossover` operator
* Changed the `Executable` interface to act as a `Callable` object (rather than using a custom `output()` method)


## 0.4.0, 9/19/2020
Expand Down
7 changes: 6 additions & 1 deletion docs/release_procedure.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,9 @@ These are the things to do when performing a release.
1. github site
1. ReadTheDocs `master` and `latest` docs
1. update pypi.org
1. update roadmap
1. update roadmap
1. change to next release + 'dev' to emphasize working on next release on
`develop` branch
1. on `develop`
1. CHANGELOG.md
1. Change `leap_ec.__version__`
56 changes: 2 additions & 54 deletions docs/source/roadmap.rst
Original file line number Diff line number Diff line change
@@ -1,61 +1,9 @@
Roadmap
=======
Please see the CHANGELOG.md for a history of feature implementations.

The LEAP development roadmap is as follows:
Future features, in no particular order of priority:

1. pre-Minimally Viable Product -- released 1/14/2020 as ``0.1-pre``
* basic support for binary representations
* bit bitflip mutation
* point-wise crossover
* uniform crossover
* basic support for real-valued representations
* mutate gaussian
* selection operators
* truncation selection
* tournament_selection selection
* random selection
* deterministic cyclic selection
* insertion selection
* continuous integration via Travis
* common test functions
* binary
* MAXONES
* real-valued, optionally translated, rotated, and scaled
* Ackley
* Cosine
* Griewank
* Langermann
* Lunacek
* Noisy Quartic
* Rastrigin
* Rosenbock
* Schwefel
* Shekel
* Spheroid
* Step
* Weierstrass
* test harnesses
* ``pytest`` supported
* simple usage examples
* canonical EAs
* genetic algorithms (GA)
* evolutionary programming (EP)
* evolutionary strategies (ES)
* simple island model
* basic run-time visualizations
* use with Jupyter notebooks
* documentation outline/stubs for ReadTheDocs
2. Minimally Viable Product, second part -- released 6/14/2020 as ``0.2.0``
* distributed / parallel fitness evaluations
* distribute local cores vs. distributed cluster nodes
* synchronous vs. asynchronous evaluations
* variable-length genomes
* Gray encoding
3. Version 0.6.0 -- released 6/13/2021
* parsimony pressure
* elitism
* integer representation
4. Future features, in no particular order of priority
* multi-objective optimization
* minimally complete documentation
* fleshed out ReadTheDocs documentation
Expand Down
5 changes: 1 addition & 4 deletions examples/advanced/external_simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@
returns a real number on its stdout, which LEAP reads as the fitness
value.
"""
"""An example of using the LEAP framework to implement an
evolutionary algorithm for tuning CARLsim models.
"""
import logging
import os
import sys
Expand Down Expand Up @@ -95,7 +92,7 @@
ea = generational_ea(max_generations=generations, pop_size=pop_size,
problem=problem, # Fitness function

# By default, the initial population is evaluated one-at-a-time.
# By default, the initial population would be evaluated one-at-a-time.
# Passing group_evaluate into init_evaluate evaluates the population in batches.
init_evaluate=ops.grouped_evaluate(problem=problem, max_individuals_per_chunk=max_individuals_per_chunk),

Expand Down
2 changes: 1 addition & 1 deletion examples/advanced/neural_network_cartpole.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def build_probes(genomes_file):
if os.environ.get(test_env_var, False) == 'True':
generations = 2
else:
generations = 10
generations = 1000

# Load the OpenAI Gym simulation
environment = gym.make('CartPole-v0')
Expand Down
2 changes: 1 addition & 1 deletion examples/advanced/real_rep_with_diversity_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
if os.environ.get(test_env_var, False) == 'True':
generations = 2
else:
generations = 100
generations = 1000

l = 2
pop_size = 10
Expand Down
12 changes: 7 additions & 5 deletions examples/simple/simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@
from leap_ec import test_env_var
from leap_ec.simple import ea_solve


# When running the test harness, just run for two generations
# (we use this to quickly ensure our examples don't get bitrot)
if os.environ.get(test_env_var, False) == 'True':
generations = 2
viz = False
else:
generations = 100
viz = True


##############################
Expand All @@ -24,12 +25,13 @@ def function(values):
"""A simple fitness function, evaluating the sum of squared parameters."""
return sum([x ** 2 for x in values])


##############################
# Entry point
##############################
if __name__ == '__main__':
ea_solve(function,
generations=generations,
bounds=[(-5.12, 5.12) for _ in range(5)],
viz=True,
mutation_std=0.1)
generations=generations,
bounds=[(-5.12, 5.12) for _ in range(5)],
viz=viz,
mutation_std=0.1)
43 changes: 43 additions & 0 deletions examples/simple/simple_distrib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""
This is the simplest example of using LEAP, where one can rely on the
very high-level function, ea_solve(), to optimize the given real-valued
function.
This differs from simple.py in that we use parallel evaluations, which is
as simple as passing in a dask Client for the `dask_client` argument.
"""
import os

from distributed import Client

from leap_ec import test_env_var
from leap_ec.simple import ea_solve

# When running the test harness, just run for two generations
# (we use this to quickly ensure our examples don't get bitrot)
if os.environ.get(test_env_var, False) == 'True':
generations = 2
viz = False
else:
generations = 100
viz = True


##############################
# Fitness function
##############################
def function(values):
"""A simple fitness function, evaluating the sum of squared parameters."""
return sum([x ** 2 for x in values])


##############################
# Entry point
##############################
if __name__ == '__main__':
ea_solve(function,
generations=generations,
bounds=[(-5.12, 5.12) for _ in range(5)],
viz=viz,
mutation_std=0.1,
dask_client=Client())
2 changes: 1 addition & 1 deletion leap_ec/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.6.0"
__version__ = "0.7.0"
37 changes: 23 additions & 14 deletions leap_ec/algorithm.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
* multi_population_ea() for invoking an EA using sub-populations
* random_search() for a more naive strategy
"""
from leap_ec import util

from leap_ec import ops, util
from toolz import pipe

from leap_ec.global_vars import context
Expand All @@ -16,8 +17,11 @@
##############################
# Function generational_ea
##############################
def generational_ea(max_generations, pop_size, problem, representation, pipeline,
stop=lambda x: False, init_evaluate=Individual.evaluate_population,
def generational_ea(max_generations, pop_size, problem, representation,
pipeline,
stop=lambda x: False,
init_evaluate=Individual.evaluate_population,
k_elites=1,
context=context):
"""
This function provides an evolutionary algorithm with a generational
Expand All @@ -39,7 +43,7 @@ def generational_ea(max_generations, pop_size, problem, representation, pipeline
:param int max_generations: The max number of generations to run the algorithm for.
Can pass in float('Inf') to run forever or until the `stop` condition is reached.
:param int pop_size: Size of the initial population
:param int stop: A function that accepts a population and
:param int stop: A function that accepts a population and
returns True iff it's time to stop evolving.
:param `Problem` problem: the Problem that should be used to evaluate
individuals' fitness
Expand All @@ -51,6 +55,7 @@ def generational_ea(max_generations, pop_size, problem, representation, pipeline
`Individual.evaluate_population` is suitable for many cases, but you
may wish to pass a different operator in for distributed evaluation
or other purposes.
:param k_elites: keep k elites
:return: a generator of `(int, individual_cls)` pairs representing the
best individual at each generation.
Expand Down Expand Up @@ -119,9 +124,12 @@ def generational_ea(max_generations, pop_size, problem, representation, pipeline
bsf = max(parents)
yield (0, bsf)

while (generation_counter.generation() < max_generations) and not stop(parents):
while (generation_counter.generation() < max_generations) and not stop(
parents):
# Execute the operators to create a new offspring population
offspring = pipe(parents, *pipeline)
offspring = pipe(parents, *pipeline,
ops.elitist_survival(parents=parents,
k=k_elites))

if max(offspring) > bsf: # Update the best-so-far individual
bsf = max(offspring)
Expand Down Expand Up @@ -155,7 +163,7 @@ def multi_population_ea(max_generations, num_populations, pop_size, problem,
Can pass in float('Inf') to run forever or until the `stop` condition is reached.
:param int num_populations: The number of separate populations to maintain.
:param int pop_size: Size of the initial population
:param int stop: A function that accepts a list of populations and
:param int stop: A function that accepts a list of populations and
returns True iff it's time to stop evolving.
:param `Problem` problem: the Problem that should be used to evaluate
individuals' fitness
Expand Down Expand Up @@ -202,7 +210,7 @@ def multi_population_ea(max_generations, num_populations, pop_size, problem,
>>> ea = multi_population_ea(max_generations=1000,
... num_populations=topology.number_of_nodes(),
... pop_size=pop_size,
...
...
... problem=problem,
...
... representation=Representation(
Expand Down Expand Up @@ -268,7 +276,8 @@ def multi_population_ea(max_generations, num_populations, pop_size, problem,
bsf = [max(p) for p in pops]
yield (0, bsf)

while (generation_counter.generation() < max_generations) and not stop(pops):
while (generation_counter.generation() < max_generations) and not stop(
pops):
# Execute each population serially
for i, parents in enumerate(pops):
# Indicate the subpopulation we are currently executing in the
Expand Down Expand Up @@ -366,7 +375,7 @@ def random_search(evaluations, problem, representation, pipeline=(),
# Function stop_at_generation()
##############################
def stop_at_generation(max_generation: int, context=context):
"""A stopping criterion function that checks the 'generation' count in the `context`
"""A stopping criterion function that checks the 'generation' count in the `context`
object and returns True iff it is >= `max_generation`.
The resulting function takes a `population` argument, which is ignored.
Expand All @@ -392,10 +401,10 @@ def stop_at_generation(max_generation: int, context=context):
>>> stop([])
True
"""
assert(max_generation >= 0)
assert(context is not None)
assert (max_generation >= 0)
assert (context is not None)

def stop(population):
return not (context['leap']['generation'] < max_generation)

return stop
Loading

0 comments on commit 01c5473

Please sign in to comment.