{
  "cells": [
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "%matplotlib inline"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "\n# Example demonstrating the use of the caching decorator.\n\nCaches the results of fitness evaluations in a pickle file\n('example_caching_cache.pkl'). To illustrate its practical use,\ncompare the runtime of this script when you first call it vs. the\nsecond time and when you comment out the decorator on\n`inner_objective`.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "import time\n\nimport numpy as np\n\nimport cgp"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "We define the target function for this example.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "def f_target(x):\n    return x ** 2 + x + 1.0"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "We then define the objective function for the evolutionary\nalgorithm: It consists of an inner objective which we wrap with the\ncaching decorator. This decorator specifies a pickle file that will be used for\ncaching results of fitness evaluations. The inner objective is then used by the objective\nfunction to compute (or retrieve from cache) the fitness of the individual.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "@cgp.utils.disk_cache(\n    \"example_caching_cache.pkl\", compute_key=cgp.utils.compute_key_from_sympy_expr_and_args\n)\ndef inner_objective(ind):\n    \"\"\"The caching decorator uses the function parameters to identify\n    identical function calls. Here, as many different genotypes\n    produce the same simplified SymPy expression we can use these\n    avoid reevaluating functionally identical individuals. Note that\n    caching only makes sense for deterministic objective functions, as\n    it assumes that identical expressions will always return the same\n    fitness values.\n\n    \"\"\"\n    expr = ind.to_sympy()\n    loss = []\n    for x0 in np.linspace(-2.0, 2.0, 100):\n        y = float(expr[0].subs({\"x_0\": x0}).evalf())\n        loss.append((f_target(x0) - y) ** 2)\n\n    time.sleep(0.25)  # emulate long fitness evaluation\n\n    return np.mean(loss)\n\n\ndef objective(individual):\n    if not individual.fitness_is_None():\n        return individual\n\n    individual.fitness = -inner_objective(individual)\n\n    return individual"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Next, we define the parameters for the population, the genome of\nindividuals, and the evolutionary algorithm.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "params = {\n    \"population_params\": {\"n_parents\": 10, \"seed\": 8188211},\n    \"ea_params\": {\n        \"n_offsprings\": 10,\n        \"tournament_size\": 1,\n        \"mutation_rate\": 0.05,\n        \"n_processes\": 1,\n    },\n    \"genome_params\": {\n        \"n_inputs\": 1,\n        \"n_outputs\": 1,\n        \"n_columns\": 10,\n        \"n_rows\": 2,\n        \"levels_back\": 2,\n        \"primitives\": (cgp.Add, cgp.Sub, cgp.Mul, cgp.ConstantFloat),\n    },\n    \"evolve_params\": {\"max_generations\": 200, \"termination_fitness\": -1e-12},\n}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "We then create a Population instance and instantiate the evolutionary algorithm.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "pop = cgp.Population(**params[\"population_params\"], genome_params=params[\"genome_params\"])\nea = cgp.ea.MuPlusLambda(**params[\"ea_params\"])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Finally, we call the `evolve` method to perform the evolutionary search.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "cgp.evolve(pop, objective, ea, **params[\"evolve_params\"], print_progress=True)\n\n\nprint(f\"evolved function: {pop.champion.to_sympy()}\")"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.8.6"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}