{
  "cells": [
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "%matplotlib inline"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "\n# Example for evolutionary regression on a piecewise target function\n\nExample demonstrating the use of Cartesian genetic programming for\nregression on a piecewise target function by using the conditional (if/else) operator.\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_piecewise_target_function.py [--max-generations=<N>]\n\n   Options:\n     -h --help\n     --max-generations=<N>  Maximum number of generations [default: 5000]\n\"\"\"\n\nimport functools\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom docopt import docopt\n\nimport cgp\n\nargs = docopt(docopt_str)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "We define a piecewise target function.  The function applies different\ntransformations to the input depending whether the input is less or greater\nthan zero. Thus to achieve high fitness,\nan individual must make use of the if/else operator.\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 ** 2 + 1.0, -x])"
      ]
    },
    {
      "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 pseudo-random points.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "def objective(individual, rng):\n    \"\"\"Objective function of the regression task.\n\n    Parameters\n    ----------\n    individual : Individual\n        Individual of the Cartesian Genetic Programming Framework.\n    rng: numpy.random.RandomState\n\n    Returns\n    -------\n    Individual\n        Modified individual with updated fitness value.\n    \"\"\"\n    if not individual.fitness_is_None():\n        return individual\n\n    n_function_evaluations = 1000\n\n    f = individual.to_numpy()\n    x = rng.uniform(-5, 5, size=(n_function_evaluations, 1))\n    y = f(x)\n\n    loss = np.mean((f_target(x) - y) ** 2)\n    individual.fitness = -loss\n\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.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "population_params = {\n    \"n_parents\": 1,\n    \"seed\": 8188211,\n}\n\ngenome_params = {\n    \"n_inputs\": 1,\n    \"n_outputs\": 1,\n    \"n_columns\": 20,\n    \"n_rows\": 1,\n    \"levels_back\": None,\n    \"primitives\": (cgp.IfElse, cgp.Mul, cgp.Add, cgp.Sub, cgp.ConstantFloat,),\n}\n\nea_params = {\"n_offsprings\": 4, \"mutation_rate\": 0.03, \"n_processes\": 2}\n\nevolve_params = {\"max_generations\": int(args[\"--max-generations\"]), \"termination_fitness\": 0.0}\n\n# create population that will be evolved\npop = cgp.Population(**population_params, genome_params=genome_params)\n\n# create instance of evolutionary algorithm\nea = cgp.ea.MuPlusLambda(**ea_params)\n\n# define callback for recording of fitness over generations\nhistory = {}\nhistory[\"fitness_champion\"] = []\nhistory[\"expr_champion\"] = []\n\n\ndef recording_callback(pop):\n    history[\"fitness_champion\"].append(pop.champion.fitness)\n    try:\n        sympy_expression = pop.champion.to_sympy()\n    except TypeError:\n        sympy_expression = pop.champion.to_sympy(simplify=False)\n\n    history[\"expr_champion\"].append(sympy_expression)\n\n\n# the objective passed to evolve should only accept one argument,\n# the individual\nrng = np.random.RandomState(seed=population_params[\"seed\"])\nobj = functools.partial(objective, rng=rng)\n\n# Perform the evolution\ncgp.evolve(pop, obj, ea, **evolve_params, print_progress=True, callback=recording_callback)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "After the evolutionary search has ended, we print the expression\nwith the highest fitness and plot the search progression and target and evolved functions.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "print(f\"Final expression {pop.champion.to_sympy()[0]} with fitness {pop.champion.fitness}\")\n\nfig = plt.figure(1)\nplt.plot(history[\"fitness_champion\"])\nplt.ylim(1.1 * min(history[\"fitness_champion\"]), 5)\nplt.xlabel(\"Generation\")\nplt.ylabel(\"Loss (Fitness)\")\nplt.legend([\"Champion loss per generation\"])\nplt.title({pop.champion.to_sympy()[0]})\nfig.savefig(\"example_piecewise_fitness_history.pdf\")\n\nx = np.arange(-5, 5, 0.01)\nx.reshape(x.size, 1)\nchampion_numpy = pop.champion.to_numpy()\n\nfig = plt.figure(2)\nplt.subplot(121)\nplt.plot(x, f_target(x), \"b\")\nplt.xlabel(\"x\")\nplt.ylabel(\"y\")\nplt.title(\"Target function\")\nplt.legend([\"target\"])\nplt.subplot(122)\nplt.plot(x, champion_numpy(x.reshape(x.size, 1)), \"r\")\nplt.xlabel(\"x\")\nplt.ylabel(\"y\")\nplt.title(\"Evolved function\")\nplt.legend([\"champion\"])\nfig.savefig(\"example_piecewise_target_function.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
}