diff --git a/Makefile b/Makefile index dc02700f..a1e07e34 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ help: venv: - python3 -m venv ./venv + python3 -m venv --system-site-packages ./venv @echo @echo Built virtual environment in ./venv @echo Run \'source venv/bin/activate\' to activate it! diff --git a/_static/asea.png b/_static/asea.png new file mode 100644 index 00000000..9ec30d9c Binary files /dev/null and b/_static/asea.png differ diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index b6531f6d..df2ff36b 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -4,9 +4,8 @@ LEAP Concepts .. toctree:: Problems - Individuals - Decoders + Representations Operators - Context Probes - Visualization \ No newline at end of file + Visualization + Context \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py index bdc740db..52695cc0 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -24,9 +24,9 @@ author = 'Jeffrey K. Bassett, Mark Coletti, and Eric O. Scott' # The short X.Y version -version = '' +version = '0.3' # The full version, including alpha/beta/rc tags -release = '' +release = 'version 0.3.1' # -- General configuration --------------------------------------------------- diff --git a/docs/source/decoder.rst b/docs/source/decoder.rst deleted file mode 100644 index 3a8bf0f7..00000000 --- a/docs/source/decoder.rst +++ /dev/null @@ -1,2 +0,0 @@ -Decoders -======== \ No newline at end of file diff --git a/docs/source/distributed.rst b/docs/source/distributed.rst new file mode 100644 index 00000000..c202f7b0 --- /dev/null +++ b/docs/source/distributed.rst @@ -0,0 +1,249 @@ +Distributed LEAP +================ +LEAP supports synchronous and asynchronous distributed concurrent fitness evaluations that +can significantly speed-up runs. LEAP uses dask (`https://dask.org/`), which +is a popular distributed processing python package, to implement +parallel fitness evaluations, and which allows easy scaling from laptops to supercomputers. + +Synchronous fitness evaluations +------------------------------- +Synchronous fitness evaluations are essentially a map/reduce approach where individuals +are fanned out to computing resources to be concurrently evaluated, and then +the calling process waits until all the evaluations are done. This is particularly +suited for by-generation approaches where offspring are evaluated in a +batch, and progress in the EA only proceeds when all individuals have been evaluated. + +Components +^^^^^^^^^^ +`leap_ec.distributed.synchronous` provides two components to implement synchronous +individual parallel evaluations. + +:leap_ec.distributed.synchronous.eval_population: + which evaluates an entire population in parallel, and returns the evaluated population +:leap_ec.distributed.synchronous.eval_pool: + is a pipeline operator that will collect offspring and then evaluate them all + at once in parallel; the evaluated offspring are returned + +Example +^^^^^^^ +The following shows a simple example of how to use the synchronous parallel +fitness evaluation in LEAP. + +.. literalinclude:: ../../examples/simple_sync_distributed.py + :linenos: + :language: python + :lines: 5-42 + +This example of a basic genetic algorithm that solves the MAX ONES problem +does not use a provided monolithic entry point, such as found with +`ea_solve()` or `generational_ea()` but, instead, directly uses LEAP's pipeline +architecture. Here, we create a simple `dask` `Client` that uses the default +local cores to do the parallel evaluations. The first step is to create the +initial random population, and then distribute those to dask workers for evaluation +via `synchronous.eval_population()`, and which returns a set of fully evaluated +parents. The `for` loop supports the number of generations we want, and provides +a sequence of pipeline operators to create offspring from selected parents. For +concurrently evaluating newly created offspring, we use `synchronous.eval_pool`, +which is just a variant of the `leap_ec.ops.pool` operator that relies on `dask` +to evaluate individuals in parallel. + +.. note:: If you wanted to use resources on a cluster or supercomputer, you would + start up `dask-scheduler` and `dask-worker`s first, and then point the `Client` + at the scheduler file used by the scheduler and workers. Distributed LEAP is agnostic on what kind of dask + client is passed as a `client` parameter -- it will generically perform the same + whether running on local cores or on a supercomputer. + +Separate Examples +^^^^^^^^^^^^^^^^^ + +There is a jupyter notebook that walks through a synchronous implementation in +`examples/simple_sync_distributed.ipynb`. The above example can also be found +at `examples/simple_sync_distributed.py`. + +Asynchronous fitness evaluations +-------------------------------- +Asynchronous fitness evaluations are a little more involved in that the EA immediately integrates +newly evaluated individuals into the population -- it doesn't wait until all +the individuals have finished evaluating before proceeding. More specifically, +LEAP implements an asynchronous steady-state evolutionary algorithm (ASEA). + +.. figure:: _static/asea.png + + Algorithm 1: Asynchronous steady-state evolutionary algorithm concurrently + updates a population as individuals are evaluated. (Mark Coletti, Eric Scott, + Jeffrey K. Bassett. **Library for Evolutionary Algorithms in Python (LEAP)**. + Genetic and Evolutionary Computation Conference, 2020. Cancun, MX. To be + printed.) + +Algorithm 1 shows the details of how an ASEA works. Newly evaluated individuals +are inserted into the population, which then leaves a computing resource available. +Offspring are created from one or more selected parents, and are then assigned +to that computing resource, thus assuring minimal idle time between evaluations. +This is particularly important within HPC contexts as it is often the case that +such resources are costly, and therefore there is an implicit need to minimize +wasting such resources. By contrast, a synchronous distributed approach risks +wasting computing resources because computing resources that finish evaluating +individuals before the last individual is evaluated will idle until the next +generation. + +Example +^^^^^^^ + +.. code-block:: Python + :linenos: + + from pprint import pformat + + from dask.distributed import Client, LocalCluster + + from leap_ec import core + from leap_ec import ops + from leap_ec import binary_problems + from leap_ec.distributed import asynchronous + from leap_ec.distributed.probe import log_worker_location, log_pop + from leap_ec.distributed.individual import DistributedIndividual + + MAX_BIRTHS = 500 + INIT_POP_SIZE = 20 + POP_SIZE = 20 + GENOME_LENGTH = 5 + + with Client(scheduler_file='scheduler.json') as client: + final_pop = asynchronous.steady_state(client, # dask client + births=MAX_BIRTHS, + init_pop_size=INIT_POP_SIZE, + pop_size=POP_SIZE, + + representation=core.Representation( + decoder=core.IdentityDecoder(), + initialize=core.create_binary_sequence( + GENOME_LENGTH), + individual_cls=DistributedIndividual), + + problem=binary_problems.MaxOnes(), + + offspring_pipeline=[ + ops.random_selection, + ops.clone, + ops.mutate_bitflip, + ops.pool(size=1)], + + evaluated_probe=track_workers_func, + pop_probe=track_pop_func) + + print(f'Final pop: \n{pformat(final_pop)}') + +The above example is quite different from the synchronous code given earlier. Unlike, +with the synchronous code, the asynchronous code does provide a monolithic function +entry point, `asynchronous.steady_state()`. The first thing to note is that +by nature this EA has a birth budget, not a generation budget, and which is set +to 500 in `MAX_BIRTHS`, and passed in via the `births` parameter. We also need +to know the size of the initial population, which is given in `init_pop_size`. +And, of course, we need the size of the population that is perpetually updated +during the lifetime of the run, and which is passed in via the `pop_size` +parameter. + +The `representation` parameter we have seen before in the other monolithic +functions, such as `generational_ea`, which encapsulates the mechanisms for +making an individual and how the individual's state is stored. In this case, +because it's the MAX ONES problem, we use the `IdentityDecoder` because we want +to use the raw bits as is, and we specify a factory function for creating +binary sequences GENOME_LENGTH in size; and, lastly, we override the default +class with a new class, `DistributedIndividual`, that contains some additional +bookkeeping useful for an ASEA, and is described later. + +The `offspring_pipeline` differs from the usual LEAP pipelines. That is, a +LEAP pipeline is usally a set of operators that define a workflow for creating offspring +from a set of prospective parents. In this case, the pipeline is for creating a +*single* offspring from an *implied* population of prospective parents to be +evaluated on a recently available dask worker; essentially, as a dask worker +finishes evaluating an individual, this pipeline will be used to create a single +offspring to be assigned to that worker for evaluation. This gives the user +maximum flexibility in how that offspring is created by choosing a selection +operator followed by perturbation operators deemed suitable for the given problem. +(Not forgetting the critical `clone` operator, the absence of which will cause +selected parents to be modified by any applied mutation or crossover operators.) + +There are two optional callback function reporting parameters, `evaluated_probe` and `pop_probe`. +`evaluated_probe` takes a single `Individual` class, or subclass, as an argument, +and can be used to write out that individual's state in a desired format. +`distributed.probe.log_worker_location` can be passed in as this argument to +write each individual's state as a CSV row to a file; by default it will write to +`sys.stdout`. The `pop_probe` parameter is similar, but allows for taking +snapshots of the hidden population at preset intervals, also in CSV format. + +Also noteworthy is that the `Client` has a `scheduler_file` specified, which +indicates that a dask scheduler and one or more dask workers have already been +started beforehand outside of LEAP and are awaiting tasking to evaluate individuals. + +There are three other optional parameters to `steady_state`, which are summarized +as follows: + +:inserter: takes a callback function of the signature `(individual, population, max_size)` + where `individual` is the newly evaluated individual that is a candidate for + inserting into the `population`, and which is the internal population that + `steady_state` updates. The value for `max_size` is passed in by `steady_state` that is the + user stipulated population size, and is used to determine if the individual + should just be inserted into the population when at the start of the run it + has yet to reach capacity. That is, when a user invokes `steady_state`, + they specify a population size via `pop_size`, and we would just normally + insert individuals until the population reaches `pop_size` in capacity, then + the function will use criteria to determine whether the individual is worthy + of being inserted. (And, if so, at the removal of an individual that was + already in the population. Or, colloquially, someone is voted off the island.) + + There are two provided inserters, `steady_state.insert_into_pop` and + `greedy_insert_into_pop`. The first will randomly select an individual from + the internal population, and will replace it if its fitness is worse than + the new individual. The second will compare the new individual with the + current worst in the population, and will replace that individual if it is + better. The default for `inserter` is to use the `greedy_insert_into_pop`. + + Of course you can write your own if either of these two inserters do not meet + your needs. + +:count_nonviable: is a boolean that, if True, means that individuals that are + non- viable are counted towards the birth budget; by default, this is `False`. A + non-viable individual is one where an exception was thrown during evaluation. + (E.g., an individual poses a deep-learner configuration that does not make + sense, such as incompatible adjacent convolutional layers, and pytorch or + tensorflow throws an exception.) + +:context: contains global state where the running number of births and non-viable individuals + is kept. This defaults to `core.context`. + +DistributedIndividual +^^^^^^^^^^^^^^^^^^^^^ +`DistributedIndividual` is a subclass of `Individual` that contains some additional +state that may be useful for distributed fitness evaluations. + +:uuid: is UUID assigned to that individual upon creation +:birth_id: is a unique, monotonically increasing integer assigned to each + indidividual on creation, and denotes its birth order +:start_eval_time: is when evaluation began for this individul, and is in `time_t` format +:stop_eval_time: when evaluation completed in `time_t` format + +This additional state is set in `distributed.evaluate.evaluate()` and +`is_viable` and `exception` are set as with the base class, `core.Individual`. + +.. note:: The `uuid` is useful if one wanted to save, say, a model or some other + state in a file; using the `uuid` in the file name will make it easier to associate + the file with a given individual later during a run's post mortem analysis. + +.. note:: The `start_eval_time` and `end_eval_time` can be useful for checking + whether individuals that take less time to evaluate come to dominate the + population, which can be important in ASEA parameter tuning. E.g., initially + the population will come to be dominated by individuals that evaluated quickly + even if they represent inferior solutions; however, eventually, better solutions + that take longer to evaluate will come to dominate the population; so, if + one observes that shorter solutions still dominate the population, then + increasing the `max_births` should be considered, if feasible, to allow time + for solutions that need longer to evaluate time to make a representative + presence in the population. + + +Separate Examples +^^^^^^^^^^^^^^^^^ +There is also a jupyter notebook walkthrough for the asynchronous implementation, +`examples/simple_async_distributed.ipynb`. Moreover, there is standalone +code in `examples/simple_async_distributed.py`. \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst index dbbd4532..bf2af577 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -13,9 +13,11 @@ Welcome to LEAP: Library for Evolutionary Algorithms in Python's documentation! Quickstart Guide Prebuilt Algorithms LEAP Concepts + Distributed LEAP LEAP Metaheuristics Building New Algorithms Cookbook + Roadmap Indices and tables diff --git a/docs/source/individual.rst b/docs/source/individual.rst deleted file mode 100644 index 94f7ba88..00000000 --- a/docs/source/individual.rst +++ /dev/null @@ -1,2 +0,0 @@ -Individuals -=========== \ No newline at end of file diff --git a/docs/source/leap.contrib.rst b/docs/source/leap.contrib.rst deleted file mode 100644 index 5205f4ab..00000000 --- a/docs/source/leap.contrib.rst +++ /dev/null @@ -1,17 +0,0 @@ -leap.contrib package -==================== - -Subpackages ------------ - -.. toctree:: - - leap.contrib.transfer - -Module contents ---------------- - -.. automodule:: leap.contrib - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/leap.contrib.transfer.rst b/docs/source/leap.contrib.transfer.rst deleted file mode 100644 index 16f2ebaf..00000000 --- a/docs/source/leap.contrib.transfer.rst +++ /dev/null @@ -1,22 +0,0 @@ -leap.contrib.transfer package -============================= - -Submodules ----------- - -leap.contrib.transfer.sequential module ---------------------------------------- - -.. automodule:: leap.contrib.transfer.sequential - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: leap.contrib.transfer - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/leap.distributed.rst b/docs/source/leap.distributed.rst deleted file mode 100644 index b5fec904..00000000 --- a/docs/source/leap.distributed.rst +++ /dev/null @@ -1,62 +0,0 @@ -leap.distributed package -======================== - -Submodules ----------- - -leap.distributed.asynchronous module ------------------------------------- - -.. automodule:: leap.distributed.asynchronous - :members: - :undoc-members: - :show-inheritance: - -leap.distributed.evaluate module --------------------------------- - -.. automodule:: leap.distributed.evaluate - :members: - :undoc-members: - :show-inheritance: - -leap.distributed.individual module ----------------------------------- - -.. automodule:: leap.distributed.individual - :members: - :undoc-members: - :show-inheritance: - -leap.distributed.logging module -------------------------------- - -.. automodule:: leap.distributed.logging - :members: - :undoc-members: - :show-inheritance: - -leap.distributed.probe module ------------------------------ - -.. automodule:: leap.distributed.probe - :members: - :undoc-members: - :show-inheritance: - -leap.distributed.synchronous module ------------------------------------ - -.. automodule:: leap.distributed.synchronous - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: leap.distributed - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/leap.rst b/docs/source/leap.rst deleted file mode 100644 index 7e4c9b5e..00000000 --- a/docs/source/leap.rst +++ /dev/null @@ -1,110 +0,0 @@ -leap package -============ - -Subpackages ------------ - -.. toctree:: - - leap.contrib - leap.distributed - -Submodules ----------- - -leap.algorithm module ---------------------- - -.. automodule:: leap.algorithm - :members: - :undoc-members: - :show-inheritance: - -leap.binary\_problems module ----------------------------- - -.. automodule:: leap.binary_problems - :members: - :undoc-members: - :show-inheritance: - -leap.brains module ------------------- - -.. automodule:: leap.brains - :members: - :undoc-members: - :show-inheritance: - -leap.core module ----------------- - -.. automodule:: leap.core - :members: - :undoc-members: - :show-inheritance: - -leap.data module ----------------- - -.. automodule:: leap.data - :members: - :undoc-members: - :show-inheritance: - -leap.ops module ---------------- - -.. automodule:: leap.ops - :members: - :undoc-members: - :show-inheritance: - -leap.probe module ------------------ - -.. automodule:: leap.probe - :members: - :undoc-members: - :show-inheritance: - -leap.problem module -------------------- - -.. automodule:: leap.problem - :members: - :undoc-members: - :show-inheritance: - -leap.real\_problems module --------------------------- - -.. automodule:: leap.real_problems - :members: - :undoc-members: - :show-inheritance: - -leap.simple module ------------------- - -.. automodule:: leap.simple - :members: - :undoc-members: - :show-inheritance: - -leap.util module ----------------- - -.. automodule:: leap.util - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: leap - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/leap.example.rst b/docs/source/leap_ec.example.rst similarity index 100% rename from docs/source/leap.example.rst rename to docs/source/leap_ec.example.rst diff --git a/docs/source/readme.rst b/docs/source/readme.rst index 1d54b43c..7ba51fd1 100644 --- a/docs/source/readme.rst +++ b/docs/source/readme.rst @@ -1,7 +1,170 @@ -.. _readme: Quickstart Guide -============ +================ + +**LEAP: Evolutionary Algorithms in Python** + +*Written by Dr. Jeffrey K. Bassett, Dr. Mark Coletti, and Eric Scott* + + .. image:: https://travis-ci.org/AureumChaos/LEAP.svg?branch=master + :target: https://travis-ci.org/AureumChaos/LEAP + + .. image:: https://coveralls.io/repos/github/AureumChaos/LEAP/badge.svg?branch=master + :target: https://coveralls.io/github/AureumChaos/LEAP?branch=master + + .. image:: https://readthedocs.org/projects/leap-gmu/badge/?version=latest + :target: https://leap-gmu.readthedocs.io/en/latest/?badge=latest + +LEAP is a general purpose Evolutionary Computation package that combines +readable and easy-to-use syntax for search and optimization algorithms with +powerful distribution and visualization features. + +LEAP's signature is its operator pipeline, which uses a simple list of +functional operators to concisely express a metaheuristic algorithm's +configuration as high-level code. Adding metrics, visualization, or +special features (like distribution, coevolution, or island migrations) +is often as simple as adding operators into the pipeline. + + +Using LEAP +---------- + +Get the stable version of LEAP from the Python package index with + +.. code-block:: bash + + pip install leap_ec + +Simple Example +************** + +The easiest way to use an evolutionary algorithm in LEAP is to use the +`leap_ec.simple` package, which contains simple interfaces for pre-built +algorithms: + +.. code-block:: python + + from leap_ec.simple import ea_solve + + def f(x): + """A real-valued function to be optimized.""" + return sum(x)**2 + + ea_solve(f, bounds=[(-5.12, 5.12) for _ in range(5)], maximize=True) + + +Genetic Algorithm Example +************************* + +The next-easiest way to use LEAP is to configure a custom algorithm via one +of the metaheuristic functions in the `leap_ec.algorithms` package. These +interfaces off you a flexible way to customize the various operators, +representations, and other components that go into a modern evolutionary +algorithm. + +Here's an example that applies a genetic algorithm variant to solve the +`MaxOnes` optimization problem. It uses bitflip mutation, uniform crossover, +and binary tournament selection: + +.. code-block:: Python + + from leap_ec.algorithm import generational_ea + from leap_ec import core, ops, binary_problems + pop_size = 5 + ea = generational_ea(generations=100, pop_size=pop_size, + problem=binary_problems.MaxOnes(), # Solve a MaxOnes Boolean optimization problem + + representation=core.Representation( + decoder=core.IdentityDecoder(), # Genotype and phenotype are the same for this task + initialize=core.create_binary_sequence(length=10) # Initial genomes are random binary sequences + ), + + # The operator pipeline + pipeline=[ops.tournament, # Select parents via tournament selection + ops.clone, # Copy them (just to be safe) + ops.mutate_bitflip, # Basic mutation: defaults to a 1/L mutation rate + ops.uniform_crossover(p_swap=0.4), # Crossover with a 40% chance of swapping each gene + ops.evaluate, # Evaluate fitness + ops.pool(size=pop_size) # Collect offspring into a new population + ]) + + print('Generation, Best_Individual') + for i, best in ea: + print(f"{i}, {best}") + + +More Examples +************* + +A number of LEAP demo applications are found in the the `example directory`_ of the github repository: + +.. _`example directory`: https://github.com/AureumChaos/LEAP/tree/master/examples + +.. code-block:: bash + + git clone https://github.com/AureumChaos/LEAP.git + python LEAP/example/island_models.py + + +.. figure:: _static/island_model_animation.gif + + Demo of LEAP running a 3-population island model on a real-valued optimization problem. + + +Documentation +------------- + +The stable version of LEAP's full documentation is over at ReadTheDocs_ + +.. _ReadTheDocs: https://leap_gmu.readthedocs.io/ + +If you want to build a fresh set of docs for yourself, you can do so after running `make setup`: + +.. code-block:: bash + + make doc + + +This will create HTML documentation in the `docs/build/html/` directory. It might take a while the first time, +since building the docs involves generating some plots and executing some example algorithms. + + +Installing from Source +---------------------- + +To install a source distribution of LEAP, clone the repo: + +.. code-block:: bash + + git clone https://github.com/AureumChaos/LEAP.git + + +And use the Makefile to install the package: + +.. code-block:: bash + + make setup + + +Run the Test Suite +****************** + +LEAP ships with a two-part `pytest` harness, divided into fast and slow tests. You can run them with + +.. code-block:: bash + + make test-fast + +and + +.. code-block:: bash + + make test-slow + + +respectively. + +.. figure:: _static/pytest_output.png + + Example of healthy PyTest output. -*(This section reproduces the contents of the project's README.md file.)* -.. mdinclude:: ../../README.md diff --git a/docs/source/representation.rst b/docs/source/representation.rst new file mode 100644 index 00000000..1f8dda0c --- /dev/null +++ b/docs/source/representation.rst @@ -0,0 +1,2 @@ +Representations +=============== \ No newline at end of file diff --git a/examples/coevolution.py b/examples/coevolution.py index 2892b7b0..f1b41845 100644 --- a/examples/coevolution.py +++ b/examples/coevolution.py @@ -1,22 +1,18 @@ """ Provides an example of a co-evolutionary system. """ - - -import numpy as np - - from leap_ec import core, ops, binary_problems from leap_ec.algorithm import multi_population_ea -import networkx as nx if __name__ == '__main__': pop_size = 5 with open('./coop_stats.csv', 'w') as log_stream: - ea = multi_population_ea(generations=1000, pop_size=pop_size, num_populations=9, - problem=binary_problems.MaxOnes(), # Fitness function + ea = multi_population_ea(generations=1000, pop_size=pop_size, + num_populations=9, + problem=binary_problems.MaxOnes(), + # Fitness function init_evaluate=ops.const_evaluate(value=-100), @@ -32,10 +28,11 @@ ops.tournament, ops.clone, ops.mutate_bitflip(expected=1), - ops.CooperativeEvaluate(context=core.context, - num_trials=1, - collaborator_selector=ops.random_selection, - log_stream=log_stream), + ops.CooperativeEvaluate( + context=core.context, + num_trials=1, + collaborator_selector=ops.random_selection, + log_stream=log_stream), ops.pool(size=pop_size) ]) diff --git a/examples/island_model.py b/examples/island_model.py index 93381391..25918ffc 100644 --- a/examples/island_model.py +++ b/examples/island_model.py @@ -99,7 +99,7 @@ def viz_plots(problems, modulo): migration_gap=50), probe.FitnessStatsCSVProbe( core.context, stream=sys.stdout) - ], + ], subpop_pipelines=subpop_probes) list(ea) diff --git a/examples/jupyter_plotting.ipynb b/examples/jupyter_plotting.ipynb index 60df50ab..6ddeb39e 100644 --- a/examples/jupyter_plotting.ipynb +++ b/examples/jupyter_plotting.ipynb @@ -59,15 +59,793 @@ }, "outputs": [ { - "ename": "ModuleNotFoundError", - "evalue": "No module named 'leap_ec'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mfrom\u001b[0m \u001b[0mleap_ec\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mcore\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mleap_ec\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mprobe\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mPopulationPlotProbe\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;31m# The probe needs access to a context object so that it can read the global generation counter\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mplot_probe\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mPopulationPlotProbe\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcore\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcontext\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mylim\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m70\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mModuleNotFoundError\u001b[0m: No module named 'leap_ec'" - ] + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " fig.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
');\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "from leap_ec import real_problems as real\n", "\n", @@ -177,9 +1745,799 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " fig.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
');\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "from leap_ec.probe import PlotTrajectoryProbe\n", "\n", @@ -230,7 +3378,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ diff --git a/examples/simple_async_distributed.ipynb b/examples/simple_async_distributed.ipynb index ba4d3601..5e93afbd 100644 --- a/examples/simple_async_distributed.ipynb +++ b/examples/simple_async_distributed.ipynb @@ -15,16 +15,16 @@ "metadata": {}, "outputs": [], "source": [ - "import sys\n", "from dask.distributed import Client, LocalCluster\n", "import toolz\n", "\n", "from leap_ec import core\n", "from leap_ec import ops\n", "from leap_ec import binary_problems\n", - "from leap_ec import util\n", + "\n", "from leap_ec.distributed import asynchronous\n", - "from leap_ec.distributed import evaluate" + "from leap_ec.distributed import evaluate\n", + "from leap_ec.distributed.individual import DistributedIndividual" ] }, { @@ -132,13 +132,13 @@ "num_offspring = 0\n", "\n", "for i, evaluated_future in enumerate(as_completed_iter):\n", - " \n", + "\n", " evaluated = evaluated_future.result()\n", - " \n", + "\n", " print(i, ', evaluated: ', evaluated.genome, evaluated.fitness)\n", - " \n", + "\n", " asynchronous.greedy_insert_into_pop(evaluated, bag, 3)\n", - " \n", + "\n", " if num_offspring < 4:\n", " # Only create offspring if we have the budget for one\n", " offspring = toolz.pipe(bag,\n", @@ -147,11 +147,11 @@ " ops.mutate_bitflip,\n", " ops.pool(size=1))\n", " print('created offspring:', offspring[0].genome)\n", - " \n", + "\n", " # Now asyncrhonously submit to dask\n", " as_completed_iter.add(client.submit(evaluate.evaluate(context=core.context), offspring[0]))\n", - " \n", - " num_offspring += 1 " + "\n", + " num_offspring += 1" ] }, { @@ -221,10 +221,13 @@ }, "outputs": [], "source": [ - "final_pop = asynchronous.steady_state(client, births=9, init_pop_size=5, pop_size=3, \n", - " initializer=core.create_binary_sequence(4), \n", - " decoder=core.IdentityDecoder(), problem=binary_problems.MaxOnes(),\n", - " offspring_pipeline=[ops.random_selection, \n", + "final_pop = asynchronous.steady_state(client, births=9, init_pop_size=5, pop_size=3,\n", + " representation=core.Representation(\n", + " decoder=core.IdentityDecoder(),\n", + " initialize=core.create_binary_sequence(4),\n", + " individual_cls=DistributedIndividual),\n", + " problem=binary_problems.MaxOnes(),\n", + " offspring_pipeline=[ops.random_selection,\n", " ops.clone,\n", " ops.mutate_bitflip,\n", " ops.pool(size=1)])" @@ -293,10 +296,13 @@ "metadata": {}, "outputs": [], "source": [ - "final_pop = asynchronous.steady_state(client, births=9, init_pop_size=5, pop_size=3, \n", - " initializer=core.create_binary_sequence(4), \n", - " decoder=core.IdentityDecoder(), problem=binary_problems.MaxOnes(),\n", - " offspring_pipeline=[ops.random_selection, \n", + "final_pop = asynchronous.steady_state(client, births=9, init_pop_size=5, pop_size=3,\n", + " representation=core.Representation(\n", + " decoder=core.IdentityDecoder(),\n", + " initialize=core.create_binary_sequence(4),\n", + " individual_cls=DistributedIndividual),\n", + " problem=binary_problems.MaxOnes(),\n", + " offspring_pipeline=[ops.random_selection,\n", " ops.clone,\n", " ops.mutate_bitflip,\n", " ops.pool(size=1)])" @@ -373,13 +379,13 @@ "pycharm": { "stem_cell": { "cell_type": "raw", + "source": [], "metadata": { "collapsed": false - }, - "source": [] + } } } }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file diff --git a/examples/simple_distributed.py b/examples/simple_async_distributed.py similarity index 63% rename from examples/simple_distributed.py rename to examples/simple_async_distributed.py index 0181046f..58b330ab 100644 --- a/examples/simple_distributed.py +++ b/examples/simple_async_distributed.py @@ -1,14 +1,31 @@ #!/usr/bin/env python3 -""" - usage: simple.py [-h] [--verbose] [--workers WORKERS] - [--init-pop-size INIT_POP_SIZE] [--max-births MAX_BIRTHS] - [--pool-size POOL_SIZE] [--scheduler-file SCHEDULER_FILE] +""" Simple example of using leap_ec.distributed.asynchronous.steady_state() + + usage: simple_async_distributed.py [-h] [--verbose] + [--track-workers-file TRACK_WORKERS_FILE] + [--track-pop-file TRACK_POP_FILE] + [--update-interval UPDATE_INTERVAL] + [--workers WORKERS] + [--init-pop-size INIT_POP_SIZE] + [--max-births MAX_BIRTHS] + [--pop-size POP_SIZE] + [--scheduler-file SCHEDULER_FILE] + [--length LENGTH] -Simple PEAL example of asynchronously distributing MAX ONES problem to workers +Simple example of asynchronously distributing MAX ONES problem to workers optional arguments: -h, --help show this help message and exit --verbose, -v Chatty output + --track-workers-file TRACK_WORKERS_FILE, -t TRACK_WORKERS_FILE + Optional file to write CSV of what host and process ID + was associated with each evaluation + --track-pop-file TRACK_POP_FILE + Optional CSV file to take regular interval snapshots + of the population ever --update-intervals + --update-interval UPDATE_INTERVAL + If using --track-pop-file, how many births before + writing an update to the specified file --workers WORKERS, -w WORKERS How many workers? --init-pop-size INIT_POP_SIZE, -s INIT_POP_SIZE @@ -18,13 +35,15 @@ at the very start of the runs --max-births MAX_BIRTHS, -m MAX_BIRTHS Maximum number of births before ending - --pool-size POOL_SIZE, -p POOL_SIZE - The size of the evaluated individuals pool + --pop-size POP_SIZE, -b POP_SIZE + The size of the evaluated individuals pop --scheduler-file SCHEDULER_FILE, -f SCHEDULER_FILE The scheduler file used to coordinate between the scheduler and workers. Specifying this option automatically triggers non-local distribution of workers, such as on a local cluster + --length LENGTH, -l LENGTH + Genome length """ import logging from pprint import pformat @@ -37,7 +56,8 @@ from leap_ec import binary_problems from leap_ec.distributed import asynchronous from leap_ec.distributed.logging import WorkerLoggerPlugin -from leap_ec.distributed.probe import log_worker_location +from leap_ec.distributed.probe import log_worker_location, log_pop +from leap_ec.distributed.individual import DistributedIndividual # Create unique logger for this namespace logger = logging.getLogger(__name__) @@ -49,6 +69,8 @@ # number of workers so that we saturate the worker pool right out of the gate. DEFAULT_INIT_POP_SIZE = DEFAULT_NUM_WORKERS +# Default number of births to update --track-pop-file +DEFAULT_UPDATE_INTERVAL = 5 if __name__ == '__main__': @@ -60,6 +82,13 @@ parser.add_argument('--track-workers-file', '-t', help='Optional file to write CSV of what host and ' 'process ID was associated with each evaluation') + parser.add_argument('--track-pop-file', + help='Optional CSV file to take regular interval' + ' snapshots of the population ever --update-intervals') + parser.add_argument('--update-interval', type=int, + default=DEFAULT_UPDATE_INTERVAL, + help='If using --track-pop-file, how many births before' + ' writing an update to the specified file') parser.add_argument('--workers', '-w', type=int, default=DEFAULT_NUM_WORKERS, help='How many workers?') parser.add_argument('--init-pop-size', '-s', type=int, @@ -120,24 +149,46 @@ else: track_workers_func = None - final_pop = asynchronous.steady_state(client, births=args.max_births, + if args.track_pop_file is not None: + track_pop_stream = open(args.track_pop_file, 'w') + track_pop_func = log_pop(args.update_interval, track_pop_stream) + else: + track_pop_func = None + + final_pop = asynchronous.steady_state(client, # dask client + births=args.max_births, init_pop_size=5, pop_size=args.pop_size, - initializer=core.create_binary_sequence( - args.length), - decoder=core.IdentityDecoder(), + + representation=core.Representation( + decoder=core.IdentityDecoder(), + initialize=core.create_binary_sequence( + args.length), + individual_cls=DistributedIndividual), + problem=binary_problems.MaxOnes(), + offspring_pipeline=[ ops.random_selection, ops.clone, ops.mutate_bitflip, ops.pool(size=1)], - evaluated_probe=track_workers_func) + + evaluated_probe=track_workers_func, + pop_probe=track_pop_func) logger.info('Final pop: \n%s', pformat(final_pop)) except Exception as e: logger.critical(str(e)) finally: - client.close() + if client is not None: + # Because an exception could have been thrown such that client does + # not exist. + client.close() + + if track_workers_func is not None: + track_workers_stream.close() + if track_pop_func is not None: + track_pop_stream.close() logger.info('Done.') diff --git a/examples/simple_ga.py b/examples/simple_ga.py index a56e0d7f..e77f0a3d 100644 --- a/examples/simple_ga.py +++ b/examples/simple_ga.py @@ -10,6 +10,7 @@ from leap_ec import ops from leap_ec import binary_problems from leap_ec import util +from leap_ec import probe def print_population(population, generation): @@ -47,7 +48,10 @@ def print_population(population, generation): offspring = pipe(parents, ops.tournament, ops.clone, + # these are optional probes to demonstrate their use + probe.print_individual(prefix='before mutation: '), ops.mutate_bitflip, + probe.print_individual(prefix='after mutation: '), ops.uniform_crossover, ops.evaluate, ops.pool(size=len(parents))) # accumulate offspring diff --git a/examples/simple_sync_distributed.py b/examples/simple_sync_distributed.py new file mode 100644 index 00000000..a50b12e0 --- /dev/null +++ b/examples/simple_sync_distributed.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +""" Simple example of using leap_ec.distributed.synchronous + +""" +import toolz +from dask.distributed import Client + +from leap_ec import core +from leap_ec import ops +from leap_ec import binary_problems +from leap_ec.distributed import synchronous + +if __name__ == '__main__': + + with Client() as client: + # create an initial population of 5 parents of 4 bits each for the + # MAX ONES problem + parents = core.Individual.create_population(5, + initialize=core.create_binary_sequence(4), + decoder=core.IdentityDecoder(), + problem=binary_problems.MaxOnes()) + + # Scatter the initial parents to dask workers for evaluation + parents = synchronous.eval_population(parents, client=client) + + for current_generation in range(5): + offspring = toolz.pipe(parents, + ops.tournament, + ops.clone, + ops.mutate_bitflip, + ops.uniform_crossover, + # Scatter offspring to be evaluated + synchronous.eval_pool(client=client, + size=len(parents))) + + print('generation:', current_generation) + [print(x.genome, x.fitness) for x in offspring] + + parents = offspring + + print('Final population:') + [print(x.genome, x.fitness) for x in parents] diff --git a/leap_ec/algorithm.py b/leap_ec/algorithm.py index 43913382..71d5e5b2 100644 --- a/leap_ec/algorithm.py +++ b/leap_ec/algorithm.py @@ -33,10 +33,7 @@ def generational_ea(generations, pop_size, representation, problem, pipeline): :param int generations: The number of generations to run the algorithm for. :param int pop_size: Size of the initial population - :param class individual_cls: class representing the (sub)type of - `Individual` the population should be generated from - :param `Decoder` decoder: the Decoder that should be used to convert - individual genomes into phenomes + :param representation: How the problem is represented in individuals :param `Problem` problem: the Problem that should be used to evaluate individuals' fitness :param initialize: a function that creates a new genome every time it is @@ -94,10 +91,7 @@ def generational_ea(generations, pop_size, representation, problem, pipeline): """ # Initialize a population of pop_size individuals of the same type as # individual_cls - parents = representation.individual_cls.create_population(pop_size, - initialize=representation.initialize, - decoder=representation.decoder, - problem=problem) + parents = representation.create_population(pop_size, problem=problem) # Evaluate initial population parents = core.Individual.evaluate_population(parents) @@ -224,10 +218,7 @@ def multi_population_ea(generations, num_populations, pop_size, problem, """ # Initialize populations of pop_size individuals of the same type as # individual_cls - pops = [representation.individual_cls.create_population(pop_size, - initialize=representation.initialize, - decoder=representation.decoder, - problem=problem) + pops = [representation.create_population(pop_size, problem=problem) for _ in range(num_populations)] # Include a reference to the populations in the context object. # This allows operators to see all of the subpopulations. diff --git a/leap_ec/core.py b/leap_ec/core.py index 613e22c1..5526c8cd 100644 --- a/leap_ec/core.py +++ b/leap_ec/core.py @@ -316,6 +316,18 @@ def __init__(self, decoder, initialize, individual_cls=Individual): self.initialize = initialize self.individual_cls = individual_cls + def create_population(self, pop_size, problem): + """ make a new population + + :param pop_size: how many individuals should be in the population + :param problem: to be solved + :return: a population of `individual_cls` individuals + """ + return self.individual_cls.create_population(pop_size, + initialize=self.initialize, + decoder=self.decoder, + problem=problem) + ############################## # Abstract Base Class Decoder diff --git a/leap_ec/distributed/asynchronous.py b/leap_ec/distributed/asynchronous.py index 21c1ab24..83f1dcfc 100644 --- a/leap_ec/distributed/asynchronous.py +++ b/leap_ec/distributed/asynchronous.py @@ -131,11 +131,13 @@ def greedy_insert_into_pop(individual, pop, max_size): # function steady_state ############################## def steady_state(client, births, init_pop_size, pop_size, - initializer, decoder, problem, offspring_pipeline, - individual_cls=DistributedIndividual, - inserter=greedy_insert_into_pop, count_nonviable=False, + representation, + problem, offspring_pipeline, + inserter=greedy_insert_into_pop, + count_nonviable=False, context=core.context, - evaluated_probe=None): + evaluated_probe=None, + pop_probe=None): """ Implements an asynchronous steady-state EA :param client: Dask client that should already be set-up @@ -143,27 +145,22 @@ def steady_state(client, births, init_pop_size, pop_size, :param init_pop_size: size of initial population sent directly to workers at start :param pop_size: how large should the population be? - :param initializer: how to initialize genomes for the first random - population - :param decoder: to to translate the genome into something the problem can - understand + :param representation: of the individuals :param problem: to be solved :param offspring_pipeline: for creating new offspring from the pop - :param individual_cls: class prototype for Individual to be used; defaults - to core.Individual since rarely do we have to subclass this. :param inserter: function with signature (new_individual, pop, popsize) used to insert newly evaluated individuals into the population; - defaults to insert_into_pop() + defaults to greedy_insert_into_pop() :param count_nonviable: True if we want to count non-viable individuals towards the birth budget :param evaluated_probe: is a function taking an individual that is given - the next evaluated individual; can be used to print this individual - as it comes in + the next evaluated individual; can be used to print newly evaluated + individuals + :param pop_probe: is an optional function that writes a snapshot of the + population to a CSV formatted stream ever N births :return: the population containing the final individuals """ - initial_population = individual_cls.create_population(init_pop_size, - initialize=initializer, - decoder=decoder, + initial_population = representation.create_population(init_pop_size, problem=problem) # fan out the entire initial population to dask workers @@ -183,7 +180,7 @@ def steady_state(client, births, init_pop_size, pop_size, if evaluated_probe is not None: # Give a chance to do something extra with the newly evaluated # individual, which is *usually* a call to - # probe.log_worker_location, but can be anything function that + # probe.log_worker_location, but can be any function that # accepts an individual as an argument evaluated_probe(evaluated) @@ -199,6 +196,9 @@ def steady_state(client, births, init_pop_size, pop_size, inserter(evaluated, pop, pop_size) + if pop_probe is not None: + pop_probe(pop) + if birth_counter.births() < births: # Only create offspring if we have the budget for one offspring = toolz.pipe(pop, *offspring_pipeline) diff --git a/leap_ec/distributed/evaluate.py b/leap_ec/distributed/evaluate.py index 3a01dc81..bd6f249a 100644 --- a/leap_ec/distributed/evaluate.py +++ b/leap_ec/distributed/evaluate.py @@ -57,7 +57,7 @@ def evaluate(individual, context=core.context): if hasattr(worker, 'logger'): worker.logger.warning( - f'Worker {worker.id}: {e} raised for {individual!s}') + f'Worker {worker.id}: {individual.exception!s} raised for {individual!s}') individual.stop_eval_time = time.time() individual.hostname = platform.node() diff --git a/leap_ec/distributed/probe.py b/leap_ec/distributed/probe.py index f1272559..1cffaa40 100644 --- a/leap_ec/distributed/probe.py +++ b/leap_ec/distributed/probe.py @@ -10,7 +10,7 @@ def log_worker_location(stream=sys.stdout, header=True): """ When debugging dask distribution configurations, this function can be used to track what machine and process was used to evaluate a given - individual + individual. Accumulates this information to the given stream as a CSV. Suitable for being passed as the `evaluated_probe` argument for leap.distributed.asynchronous.steady_state(). @@ -52,3 +52,54 @@ def write_record(individual): stream.flush() return write_record + + +def log_pop(update_interval, stream=sys.stdout, header=True): + """ Regularly update a CSV formatted stream with snapshots of the given + population. + + This is useful for asynchronous.steady_state() to regularly probe the + regularly updated population. + + :param update_interval: how often should we write a row? + :param stream: open stream to which to write rows + :param header: True if we want a header for the CSV file + :return: a function for saving regular population snapshots + """ + stream = stream + writer = csv.DictWriter(stream, + fieldnames=['interval', 'uuid', 'birth_id', + 'start_eval_time', 'stop_eval_time', + 'fitness']) + interval = 0 # current update interval that is incremented every + + if header: + writer.writeheader() + + def write_pop_update(population): + """ + + :param population: to be written to stream + :return: None + """ + nonlocal stream + nonlocal writer + nonlocal interval + nonlocal update_interval + + if interval % update_interval == 0: + for individual in population: + writer.writerow({'interval': interval, + 'uuid': individual.uuid, + 'birth_id': individual.birth_id, + 'start_eval_time': individual.start_eval_time, + 'stop_eval_time': individual.stop_eval_time, + 'fitness': individual.fitness}) + + # On some systems, such as Summit, we need to force a flush else there + # will be no output until the very end of the run. + stream.flush() + + interval += 1 + + return write_pop_update \ No newline at end of file diff --git a/leap_ec/probe.py b/leap_ec/probe.py index 00030841..c51c9ca3 100644 --- a/leap_ec/probe.py +++ b/leap_ec/probe.py @@ -3,12 +3,15 @@ import csv import sys +from typing import Iterator + from matplotlib import pyplot as plt import numpy as np import pandas as pd from toolz import curry from leap_ec import ops as op +from leap_ec.ops import iteriter_op ############################## @@ -30,6 +33,28 @@ def print_probe(population, probe, stream=sys.stdout, prefix=''): return population +############################## +# print_individual +############################## +@curry +@iteriter_op +def print_individual(next_individual: Iterator, prefix='', stream=sys.stdout) -> Iterator: + """ Just echoes the individual from within the pipeline + + Uses next_individual.__str__ + + :param next_individual: iterator for next individual to be printed + :return: the same individual, unchanged + """ + + while True: + individual = next(next_individual) + + print(f'{prefix}{individual!s}', file=stream) + + yield individual + + ############################## # BestSoFar probe ############################## diff --git a/setup.py b/setup.py index 4a7aefb1..25b48168 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name='leap_ec', - version='0.2.0', + version='0.3.1', packages=find_packages(), license='Academic', author='Mark Coletti, Eric Scott, Jeff Bassett',