{
  "cells": [
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "%matplotlib inline"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "\n# Example for evolutionary regression with multiple genomes\n\nExample demonstrating the use of Cartesian genetic programming with multiple\ngenomes per individual for a simple regression task with a piecewise\ntarget function.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "# The docopt str is added explicitly to ensure compatibility with\n# sphinx-gallery.\ndocopt_str = \"\"\"\n   Usage:\n     example_multi_genome.py [--max-generations=<N>]\n\n   Options:\n     -h --help\n     --max-generations=<N>  Maximum number of generations [default: 300]\n\"\"\"\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport scipy.constants\nfrom docopt import docopt\n\nimport cgp\n\nargs = docopt(docopt_str)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "We first define a target function.  The function applies different\ntransformations to the input depending whether the input is less or greater\nthan or equal to zero. We thus need to fit two different functions.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "def f_target(x):\n    return np.select([x < 0, x >= 0], [-x, x ** 2 + 1.0])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Then we define the objective function for the evolution. It uses the\nmean-squared error between the output of the expression represented by a given\nindividual and the target function evaluated on a set of random points. We\nhere either evaluate the function represented by the first (``f[0]``) or the second\ngenome (``f[1]``), depending whether the input is less or greater than zero.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "def objective(individual):\n    if not individual.fitness_is_None():\n        return individual\n\n    n_function_evaluations = 1000\n\n    np.random.seed(1234)\n\n    # Note that f is now a list of functions because individual is an instance\n    # of `InvidividualMultiGenome`\n    f = individual.to_numpy()\n    x = np.random.uniform(-4, 4, (n_function_evaluations, 1))\n    y = np.piecewise(x, [x[:, 0] < 0, x[:, 0] >= 0], f)[:, 0]\n    loss = np.sum((f_target(x[:, 0]) - y) ** 2)\n    individual.fitness = -loss / n_function_evaluations\n    return individual"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Next, we set up the evolutionary search. First, we define the parameters for\nthe population, the genomes of individuals, and the evolutionary\nalgorithm. Note that we define ``genome_params`` as a list of parameter\ndictionaries which causes the population to create instances of\n``InvidividualMultiGenome``.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "population_params = {\"n_parents\": 1, \"seed\": 8188211}\n\nsingle_genome_params = {\n    \"n_inputs\": 1,\n    \"n_outputs\": 1,\n    \"n_columns\": 12,\n    \"n_rows\": 1,\n    \"levels_back\": 5,\n    \"primitives\": (cgp.Add, cgp.Sub, cgp.Mul, cgp.ConstantFloat),\n}\ngenome_params = [single_genome_params, single_genome_params]\n\nea_params = {\"n_offsprings\": 4, \"mutation_rate\": 0.03, \"n_processes\": 1}\n\nevolve_params = {\"max_generations\": int(args[\"--max-generations\"]), \"termination_fitness\": 0.0}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "We create a population that will be evolved\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "pop = cgp.Population(**population_params, genome_params=genome_params)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "and an instance of the (mu + lambda) evolutionary algorithm\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "ea = cgp.ea.MuPlusLambda(**ea_params)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "We define a callback for recording of fitness over generations\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "history = {}\nhistory[\"fitness_champion\"] = []\n\n\ndef recording_callback(pop):\n    history[\"fitness_champion\"].append(pop.champion.fitness)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "and finally perform the evolution\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "cgp.evolve(pop, objective, ea, **evolve_params, print_progress=True, callback=recording_callback)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "After finishing the evolution, we print the evolved expression and plot the result.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "expr = pop.champion.to_sympy()\nprint(expr)\nprint(f\"--> x<=0: {expr[0]}, \\n    x> 0: {expr[1]}\")\n\nwidth = 9.0\nfig, axes = plt.subplots(1, 2, figsize=(width, width / scipy.constants.golden))\n\nax_fitness, ax_function = axes[0], axes[1]\nax_fitness.set_xlabel(\"Generation\")\nax_fitness.set_ylabel(\"Fitness\")\n\nax_fitness.plot(history[\"fitness_champion\"], label=\"Champion\")\n\nax_fitness.set_yscale(\"symlog\")\nax_fitness.set_ylim(-1.0e2, 0.1)\nax_fitness.axhline(0.0, color=\"0.7\")\n\nf = pop.champion.to_numpy()\nx = np.linspace(-5.0, 5.0, 20)[:, np.newaxis]\n\ny = np.piecewise(x, [x[:, 0] < 0, x[:, 0] >= 0], f)[:, 0]\ny_target = f_target(x[:, 0])\n\nax_function.plot(x, y_target, lw=2, alpha=0.5, label=\"Target\")\nax_function.plot(x, y, \"x\", label=\"Champion\")\nax_function.legend()\nax_function.set_ylabel(r\"$f(x)$\")\nax_function.set_xlabel(r\"$x$\")\n\nfig.savefig(\"example_multi_genome.pdf\", dpi=300)"
      ]
    }
  ],
  "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
}