{ "cells": [ { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false, "slideshow": { "slide_type": "skip" } }, "outputs": [ ], "source": [ "from itertools import product\n", "import numpy as np\n", "import plotly.graph_objects\n", "from ipywidgets import HBox, Layout, interact as _original_interact" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false, "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/html": [ "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 2, "metadata": { }, "output_type": "execute_result" } ], "source": [ "%%html\n", "\n", "" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "_original_interact = interact\n", "def interact(_function_to_wrap=None, _layout=\"horizontal\", **kwargs):\n", " \"\"\"interact, but with widgets laid out in a horizontal flexbox layout\n", "\n", " This function works exactly like 'interact' (from SageMath or ipywidgets), \n", " except that instead of putting all of the widgets into a vertical box \n", " (VBox), it uses a horizontal box (HBox) by default. The HBox uses a flexbox \n", " layout, so that if there are many widgets, they'll wrap onto a second row. \n", " \n", " Options:\n", " '_layout' - 'horizontal' by default. Anything else, and it will revert \n", " back to using the default layout of 'interact' (a VBox). \n", " \"\"\"\n", " def decorator(f):\n", " retval = _original_interact(f, **kwargs)\n", " if _layout == \"horizontal\":\n", " widgets = retval.widget.children[:-1]\n", " output = retval.widget.children[-1]\n", " hbox = HBox(widgets, layout=Layout(flex_flow=\"row wrap\"))\n", " retval.widget.children = (hbox, output)\n", " return retval\n", " if _function_to_wrap is None:\n", " # No function passed in, so this function must *return* a decorator\n", " return decorator\n", " # This function was called directly, *or* was used as a decorator directly\n", " return decorator(_function_to_wrap)\n" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "def find_zeros(field, *ranges, **options):\n", " \"\"\"Numerically approximate all zeros of a vector field within a box\n", "\n", " Each 'range' should have the form (x, xmin, xmax), where 'x' is one of the\n", " variables appearing in the vector field, and 'xmin' and 'xmax' are the\n", " bounds on that variables.\n", "\n", " Options:\n", " 'intervals' - How many subintervals to use in each dimension (default\n", " 20). If there are zeros that are very close together, you may need\n", " to increase this to find them. But be warned that the running time\n", " is roughly this to the n power, where n is the number of variables.\n", " 'tolerance' - How close to approximate roots, roughly (default 1e-4)\n", " 'maxiter' - Maximum number of iterations of Newton's Method to run for\n", " any one (potential) solution. This defaults to -2*log(tolerance).\n", " There usually isn't much harm in increasing this, unless there are\n", " many false hits.\n", " 'round' - Round the components of the solutions to this many decimal\n", " places (default None, meaning do not round them at all)\n", " \"\"\"\n", "\n", " # Initialization\n", " intervals = options.get(\"intervals\", 20)\n", " tolerance = options.get(\"tolerance\", 1e-5)\n", " maxiter = options.get(\"maxiter\", int(round(-2*log(tolerance))))\n", " roundto = options.get(\"round\", None)\n", " n = len(ranges)\n", " if len(field) != n:\n", " raise ValueError(\"Dimension of vector field is {}, but {} ranges \"\n", " \"given\".format(len(field), n))\n", "\n", " mins = [xmin - (xmax - xmin)/intervals*tolerance for x, xmin, xmax in ranges]\n", " maxes = [xmax + (xmax - xmin)/intervals*tolerance for x, xmin, xmax in ranges]\n", " deltas = [(xmax - xmin) / intervals for xmin, xmax in zip(mins, maxes)]\n", " powers = [1 << i for i in range(n)]\n", " J = jacobian(field, [x for x, xmin, xmax in ranges])\n", " def dist(v, w):\n", " return sqrt(sum(((a - b) / d)**2 for a, b, d in zip(v, w, deltas)))\n", "\n", " # Set up the array of positive/negative signs of the vector field\n", " signs = np.zeros((intervals + 1,) * n, dtype=int)\n", " sranges = [srange(m, m + d*(intervals + 0.5), d) for m, d in zip(mins, deltas)]\n", " for vertex, index in zip(product(*sranges), product(range(intervals + 1), repeat=n)):\n", " v = field(*vertex)\n", " signs[index] = sum(powers[i] for i in range(n) if v[i] > 0)\n", "\n", " # Now search through that array for potential solutions\n", " solutions = []\n", " mask = int((1 << n) - 1)\n", " for index in product(range(intervals), repeat=n):\n", " indexpowers = list(zip(index, powers))\n", " all0s = 0\n", " all1s = mask\n", " for k in range(mask + 1):\n", " newindex = [i + (1 if k & p else 0) for i, p in indexpowers]\n", " vertex = signs[tuple(newindex)]\n", " all0s |= vertex\n", " all1s &= vertex\n", " if all0s & ~all1s == mask:\n", " # Now do Newton's method!\n", " v = vector(m + d*(i + 0.5) for i, m, d in zip(index, mins, deltas))\n", " for i in range(maxiter):\n", " previous_v = v\n", " v = v - J(*v).solve_right(field(*v))\n", " if dist(v, previous_v) < tolerance:\n", " break\n", " else:\n", " warn(\"{} iterations reached without convergence for solution \"\n", " \"{}\".format(maxiter, v), RuntimeWarning)\n", " if solutions and min(dist(v, w) for w in solutions) < 2*tolerance:\n", " continue\n", " if not all(xmin <= x <= xmax for x, xmin, xmax in zip(v, mins, maxes)):\n", " continue\n", " solutions.append(vector(RDF, v))\n", "\n", " # A convenience: round to some number of decimal places, if requested\n", " if roundto is not None:\n", " solutions = [vector(round(x, roundto) for x in v) for v in solutions]\n", " return solutions\n" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false, "slideshow": { "slide_type": "skip" } }, "outputs": [ ], "source": [ "def find_zeros1d(f, x_range, **options):\n", " x, xmin, xmax = x_range\n", " intervals = options.get(\"intervals\", 20)\n", " tolerance = options.get(\"tolerance\", 1e-5)\n", " f = fast_float(f, x)\n", "\n", " x_min = xmin - (xmax - xmin)/intervals*tolerance\n", " x_max = xmax + (xmax - xmin)/intervals*tolerance\n", " delta = (xmax - xmin) / intervals\n", " x_values = np.linspace(x_min, x_max, intervals + 1)\n", " f_values = np.array([int(f(x0) > 0) for x0 in x_values])\n", " sign_changes = (f_values[:-1] - f_values[1:]).nonzero()[0]\n", " solutions = []\n", " for i in sign_changes:\n", " x0 = find_root(f, x_values[i], x_values[i+1])\n", " if solutions and min([abs(x0 - a) for a in solutions]) < 2*tolerance:\n", " continue\n", " solutions.append(x0)\n", " return solutions\n" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ ], "source": [ "# Our mixin class for Figure and FigureWidget. Should never be instantiated! \n", "class MyFigure(object):\n", " def add(self, *items, subplot=None):\n", " if subplot is not None:\n", " try:\n", " subplot[0]\n", " except:\n", " subplot = (1, subplot)\n", " row = int(subplot[0])\n", " col = int(subplot[1])\n", " retval = []\n", " text_indices = []\n", " text3d_indices = []\n", " for item in items:\n", " if isinstance(item, plotly.graph_objects.layout.Annotation):\n", " if subplot is None:\n", " self.add_annotation(item)\n", " else:\n", " self.add_annotation(item, row=row, col=col)\n", " text_indices.append(len(retval))\n", " retval.append(None)\n", " elif isinstance(item, plotly.graph_objects.layout.scene.Annotation):\n", " self.layout.scene.annotations += (item,)\n", " text3d_indices.append(len(retval))\n", " retval.append(None)\n", " else:\n", " if subplot is None:\n", " self.add_trace(item)\n", " else:\n", " self.add_trace(item, row=row, col=col)\n", " retval.append(self.data[-1])\n", " for i, pos in enumerate(text_indices, start=-len(text_indices)):\n", " retval[pos] = self.layout.annotations[i]\n", " for i, pos in enumerate(text3d_indices, start=-len(text3d_indices)):\n", " retval[pos] = self.layout.scene.annotations[i]\n", " if len(retval) == 1:\n", " return retval[0]\n", " return retval\n", "\n", " def __iadd__(self, item):\n", " if isinstance(item, plotly.graph_objects.layout.Annotation):\n", " self.add_annotation(item)\n", " elif isinstance(item, plotly.graph_objects.layout.scene.Annotation):\n", " self.layout.scene.annotations += (item,)\n", " else:\n", " self.add_trace(item)\n", " return self\n", "\n", " def axes_labels(self, *labels):\n", " if len(labels) == 2:\n", " self.layout.xaxis.title.text = labels[0]\n", " self.layout.yaxis.title.text = labels[1]\n", " elif len(labels) == 3:\n", " self.layout.scene.xaxis.title.text = labels[0]\n", " self.layout.scene.yaxis.title.text = labels[1]\n", " self.layout.scene.zaxis.title.text = labels[2]\n", " else:\n", " raise ValueError(\"You must specify labels for either 2 or 3 axes.\")\n", "\n", " def axes_ranges(self, *ranges, scale=None):\n", " if len(ranges) == 2:\n", " (xmin, xmax), (ymin, ymax) = ranges\n", " self.layout.xaxis.range = (xmin, xmax)\n", " self.layout.yaxis.range = (ymin, ymax)\n", " if scale is not None:\n", " x, y = scale\n", " self.layout.xaxis.constrain = \"domain\"\n", " self.layout.yaxis.constrain = \"domain\"\n", " self.layout.yaxis.scaleanchor = \"x\"\n", " self.layout.yaxis.scaleratio = y / x\n", " elif len(ranges) == 3:\n", " (xmin, xmax), (ymin, ymax), (zmin, zmax) = ranges\n", " self.layout.scene.xaxis.range = (xmin, xmax)\n", " self.layout.scene.yaxis.range = (ymin, ymax)\n", " self.layout.scene.zaxis.range = (zmin, zmax)\n", " if isinstance(scale, str):\n", " self.layout.scene.aspectmode = scale\n", " elif scale is not None:\n", " x, y, z = scale\n", " x *= xmax - xmin\n", " y *= ymax - ymin\n", " z *= zmax - zmin\n", " c = sorted((x, y, z))[1]\n", " self.layout.scene.aspectmode = \"manual\"\n", " self.layout.scene.aspectratio.update(x=x/c, y=y/c, z=z/c)\n", " else:\n", " raise ValueError(\"You must specify ranges for either 2 or 3 axes.\")\n", "\n", "\n", "class Figure(plotly.graph_objects.Figure, MyFigure):\n", " def __init__(self, *args, **kwargs):\n", " specs = kwargs.pop(\"subplots\", None)\n", " if specs is None:\n", " super().__init__(*args, **kwargs)\n", " else:\n", " try:\n", " specs[0][0]\n", " except:\n", " specs = [specs]\n", " rows = len(specs)\n", " cols = len(specs[0])\n", " fig = plotly.subplots.make_subplots(rows=rows, cols=cols, \n", " specs=specs, **kwargs)\n", " super().__init__(fig)\n", "\n", "\n", "class FigureWidget(plotly.graph_objects.FigureWidget, MyFigure):\n", " def __init__(self, *args, **kwargs):\n", " self._auto_items = []\n", " specs = kwargs.pop(\"subplots\", None)\n", " if specs is None:\n", " super().__init__(*args, **kwargs)\n", " else:\n", " try:\n", " specs[0][0]\n", " except:\n", " specs = [specs]\n", " rows = len(specs)\n", " cols = len(specs[0])\n", " fig = plotly.subplots.make_subplots(rows=rows, cols=cols, \n", " specs=specs, **kwargs)\n", " super().__init__(fig)\n", "\n", " def auto_update(self, *items):\n", " if self._auto_items:\n", " for item, newitem in zip(self._auto_items, items):\n", " if isinstance(newitem, tuple):\n", " newitem = newitem[0]\n", " item.update(newitem)\n", " else:\n", " for item in items:\n", " if isinstance(item, tuple):\n", " item, subplot = item\n", " self._auto_items.append(self.add(item, subplot=subplot))\n", " else:\n", " self._auto_items.append(self.add(item))\n", "\n", " def initialized(self):\n", " return len(self._auto_items) > 0\n", "\n", "\n", "# Below are all the actual plotting methods. First, the 2D graphics:\n", "def plotly_text(text, location, **options):\n", " options = options.copy()\n", " x, y = np.array(location, dtype=float)\n", " if options.pop(\"update\", False):\n", " return dict(text=text, x=x, y=y)\n", " options.setdefault(\"font_color\", options.pop(\"color\", \"black\"))\n", " size = options.pop(\"size\", None)\n", " if size is not None:\n", " options.setdefault(\"font_size\", size)\n", " arrow = options.pop(\"arrow\", None)\n", " if arrow:\n", " options.setdefault(\"ax\", float(arrow[0]))\n", " options.setdefault(\"ay\", float(arrow[1]))\n", " options.setdefault(\"showarrow\", True)\n", " else:\n", " options.setdefault(\"showarrow\", False)\n", " if options.pop(\"paper\", False):\n", " options.update(xref=\"paper\", yref=\"paper\")\n", " options.setdefault(\"xanchor\", \"left\")\n", " options.setdefault(\"yanchor\", \"bottom\")\n", " return plotly.graph_objects.layout.Annotation(\n", " text=text, x=x, y=y, **options)\n", "\n", "\n", "def plotly_points(points, **options):\n", " options = options.copy()\n", " x, y = np.array(points, dtype=float).transpose()\n", " if options.pop(\"update\", False):\n", " return dict(x=x, y=y)\n", " options.setdefault(\"marker_color\", options.pop(\"color\", \"black\"))\n", " options.setdefault(\"marker_size\", options.pop(\"size\", 8))\n", " options.setdefault(\"mode\", \"markers\")\n", " return plotly.graph_objects.Scatter(x=x, y=y, **options)\n", "\n", "\n", "def plotly_lines(points, **options):\n", " options = options.copy()\n", " x, y = np.array(points, dtype=float).transpose()\n", " if options.pop(\"update\", False):\n", " return dict(x=x, y=y)\n", " color = options.pop(\"color\", \"blue\")\n", " options.setdefault(\"line_color\", color)\n", " options.setdefault(\"mode\", \"lines\")\n", " return plotly.graph_objects.Scatter(x=x, y=y, **options)\n", "\n", "\n", "def plotly_function(f, x_range, **options):\n", " options = options.copy()\n", " plotpoints = options.pop(\"plotpoints\", 81)\n", " color = options.pop(\"color\", \"blue\")\n", " options.setdefault(\"line_color\", color)\n", " options.setdefault(\"mode\", \"lines\")\n", " options.setdefault(\"line_shape\", \"spline\")\n", " options.setdefault(\"line_smoothing\", 1.3)\n", " x, xmin, xmax = x_range\n", " f = fast_float(f, x)\n", " x = np.linspace(float(xmin), float(xmax), plotpoints)\n", " y = np.array([f(x0) for x0 in x])\n", " if options.pop(\"update\", False):\n", " return dict(x=x, y=y)\n", " return plotly.graph_objects.Scatter(x=x, y=y, **options)\n", "\n", "\n", "def plotly_parametric(f, t_range, **options):\n", " options = options.copy()\n", " plotpoints = options.pop(\"plotpoints\", 101)\n", " color = options.pop(\"color\", \"blue\")\n", " options.setdefault(\"line_color\", color)\n", " options.setdefault(\"mode\", \"lines\")\n", " options.setdefault(\"line_shape\", \"spline\")\n", " options.setdefault(\"line_smoothing\", 1.3)\n", " t, tmin, tmax = t_range\n", " f1, f2 = [fast_float(f_, t) for f_ in f]\n", " t = np.linspace(float(tmin), float(tmax), plotpoints)\n", " x, y = np.array([(f1(t0), f2(t0)) for t0 in t]).transpose()\n", " if options.pop(\"update\", False):\n", " return dict(x=x, y=y)\n", " return plotly.graph_objects.Scatter(x=x, y=y, **options)\n", "\n", "\n", "def plotly_points3d(points, **options):\n", " options = options.copy()\n", " options.setdefault(\"marker_color\", options.pop(\"color\", \"black\"))\n", " options.setdefault(\"marker_size\", options.pop(\"size\", 2.5))\n", " options.setdefault(\"mode\", \"markers\")\n", " x, y, z = np.array(points, dtype=float).transpose()\n", " if options.pop(\"update\", False):\n", " return dict(x=x, y=y, z=z)\n", " return plotly.graph_objects.Scatter3d(x=x, y=y, z=z, **options)\n", "\n", "\n", "def plotly_function3d(f, x_range, y_range, **options):\n", " options = options.copy()\n", " plotpoints = options.pop(\"plotpoints\", 81)\n", " try:\n", " plotpointsx, plotpointsy = plotpoints\n", " except:\n", " plotpointsx = plotpointsy = plotpoints\n", " color = options.pop(\"color\", \"lightblue\")\n", " options.setdefault(\"colorscale\", (color, color))\n", " x, xmin, xmax = x_range\n", " y, ymin, ymax = y_range\n", " f = fast_float(f, x, y)\n", " x = np.linspace(float(xmin), float(xmax), plotpointsx)\n", " y = np.linspace(float(ymin), float(ymax), plotpointsy)\n", " x, y = np.meshgrid(x, y)\n", " xy = np.array([x.flatten(), y.flatten()]).transpose()\n", " z = np.array([f(x0, y0) for x0, y0 in xy]).reshape(plotpointsy, plotpointsx)\n", " if options.pop(\"update\", False):\n", " return dict(x=x, y=y, z=z)\n", " return plotly.graph_objects.Surface(x=x, y=y, z=z, **options)\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "source": [ "# Learning goals: \n", "\n", "- Be able to explain the significance of optimization in biology, and give several examples. \n", "- Be able to describe the main biological process that underlies all optimization problems in biology. \n", "- Be able to distinguish *local maxima* and *local minima* of a function from global extrema. \n", "- Know the significance of local maxima in evolution. \n" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "source": [ "## Example 1: Optimal foraging\n", "
\n", "\n", "Consider a bird or small mammal that has a nest, and forages for food in some area around its nest. \n", "\n", "- The larger that area, the more food it can gather, so the more **energy** it gains, or the more energy it is able to provide for its offspring. \n", "- But the larger the area, the more energy it must spend defending its territory from competitors. \n", "\n", "What should be optimized here? \n", "
\n" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "5fd10dea0cab4c46b6b31f8a2950f2b9", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Interactive function .update at 0x7f01cfd71550> with 3 widgets\n", " r: Tra…" ] }, "execution_count": 7, "metadata": { }, "output_type": "execute_result" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "cba180ee895a4de1853946ea066e6e49", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FigureWidget({\n", " 'data': [{'marker': {'color': [#35b779, #fde725, #440154],\n", " 'colors…" ] }, "execution_count": 7, "metadata": { }, "output_type": "execute_result" } ], "source": [ "def foraging_interactive():\n", " energy_in(x) = 0.0001 * pi*x^2\n", " energy_out(x) = 0.000001 * x^3\n", "\n", " figure = FigureWidget(subplots=[[{\"rowspan\": 2}, {}], [None, {}]])\n", " figure.axes_ranges((-322, 322), (-322, 350), scale=(1, 1))\n", " figure.layout.xaxis1.visible = False\n", " figure.layout.yaxis1.visible = False\n", " figure.layout.yaxis2.range = [-2, 33]\n", " figure.layout.xaxis3.range = [0, 320]\n", " figure.layout.yaxis3.range = [-1, 5]\n", " figure.layout.yaxis2.title.text = r\"Energy (kcal/day)\"\n", " figure.layout.xaxis3.title.text = r\"Foraging radius (meters)\"\n", " figure.layout.yaxis3.title.text = r\"Net energy gain
(kcal/day)\"\n", " figure.layout.showlegend = False\n", " figure.add(plotly_text(\"Foraging area\", (0,350), font_size=18), subplot=(1,1))\n", " figure.add(plotly_text(\"Nest\", (0,-2), font_size=12, color=\"saddlebrown\", yanchor=\"top\"), subplot=(1,1))\n", " color1 = plotly.colors.sequential.Viridis[6]\n", " color2 = plotly.colors.sequential.Viridis[0]\n", " color3 = plotly.colors.sequential.Viridis[9]\n", " labels = [\"Energy from food
(∝ area)\", \"Energy spent
defending territory\", \"Net energy gain\"]\n", " energy_chart = plotly.graph_objects.Bar(x=labels, orientation=\"v\", \n", " marker_color=[color1, color3, color2], marker_colorscale=\"Viridis\")\n", " energy_chart = figure.add(energy_chart, subplot=(1,2))\n", " netenergy_graph = plotly_function(energy_in - energy_out, (x, 0, 1), color=color2)\n", " netenergy_graph = figure.add(netenergy_graph, subplot=(2,2))\n", " maximum = find_root((energy_in - energy_out).derivative(), 10, 300)\n", " maxline = plotly_lines([(maximum, -0.5), (maximum, 6)], color=color1, line_dash=\"dash\")\n", " maxline = figure.add(maxline, subplot=(2, 2))\n", " maxlabel = plotly_text(fr\"$r = {round(maximum, 1)}\\,\\text{{m}}$\", \n", " (maximum - 0.5, 2), font_size=14, xanchor=\"right\", xref=\"x3\", yref=\"y3\")\n", " maxlabel = figure.add(maxlabel)\n", "\n", " @interact(r=slider(1, 320, 1, default=100, label=\"Radius\"), \n", " show_graph=checkbox(False, label=\"Show graph\"), \n", " show_max=checkbox(False, label=\"Show maximum\"))\n", " def update(r, show_graph, show_max):\n", " initialized = figure.initialized()\n", " f(t) = (r*cos(t), r*sin(t))\n", " energy = np.array([energy_in(r), energy_out(r), energy_in(r) - energy_out(r)], dtype=float)\n", " forage_area = plotly_parametric(f, (t, 0, 2*pi), plotpoints=41, \n", " fill=\"toself\", color=color1, fillcolor=color1, update=initialized)\n", " nest = plotly_points([(0,0)], color=\"saddlebrown\")\n", " netenergy_update = plotly_function(energy_in - energy_out, (x, 0, r), update=True)\n", " with figure.batch_update():\n", " figure.auto_update(forage_area, nest)\n", " energy_chart.update(y=energy)\n", " netenergy_graph.update(netenergy_update)\n", " figure.layout.xaxis3.visible = show_graph\n", " figure.layout.yaxis3.visible = show_graph\n", " netenergy_graph.visible = show_graph\n", " maxline.visible = show_graph and show_max and r > maximum\n", " maxlabel.visible = show_graph and show_max and r > maximum\n", " if show_graph:\n", " figure.layout.yaxis2.domain = [0.42, 1]\n", " figure.layout.yaxis3.domain = [0, 0.28]\n", " else:\n", " figure.layout.yaxis2.domain = [0.1, 1]\n", " figure.layout.yaxis3.domain = [0, 0.1]\n", "\n", " return figure\n", "\n", "foraging_interactive()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "source": [ "## Example 2: Optimal clutch size\n", "
\n", "\n", "Birds reproduce annually at a certain time of year. The group of eggs a bird lays at one time is called its ***clutch***. So the number of eggs laid is called its ***clutch size***. \n", "\n", "- The ***survival rate*** is the probability that any one egg will survive to adulthood, until it too can reproduce. \n", "- We can also think of the survival rate as the *fraction* of eggs from a clutch that will survive, on average. (expected value) \n", "- The more eggs in a clutch, the more difficult it is for the parents to provide for them, protect them, etc. So as the clutch size increases, the survival rate *decreases*. \n", "\n", "What should be optimized here? \n", "
\n" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "9db2d3d772874cd4a3ddcc6ff75f5e5c", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Interactive function .update at 0x7f01cfc3a790> with 3 widgets\n", " C: T…" ] }, "execution_count": 8, "metadata": { }, "output_type": "execute_result" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "f629308313a943f3a95bdccf31e4d481", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FigureWidget({\n", " 'data': [{'domain': {'x': [0, 0.45], 'y': [0.44, 0.95]},\n", " 'labels': [Don't sur…" ] }, "execution_count": 8, "metadata": { }, "output_type": "execute_result" } ], "source": [ "def clutchsize_interactive():\n", " survival(x) = (1 - x/9)^2\n", "\n", " figure = FigureWidget(subplots=[[{\"type\": \"pie\"}, {}], [{}, {}]])\n", " figure.layout.yaxis1.range = [0, 9.5]\n", " figure.layout.xaxis2.range = [0, 9.5]\n", " figure.layout.yaxis2.range = [0, 1]\n", " figure.layout.yaxis2.rangemode = \"tozero\"\n", " figure.layout.yaxis2.domain = [0, 0.28]\n", " figure.layout.xaxis3.range = [0, 9.5]\n", " figure.layout.yaxis3.range = [0, 1.5]\n", " figure.layout.yaxis1.title.text = r\"# of offspring\"\n", " figure.layout.xaxis2.title.text = r\"Clutch size\"\n", " figure.layout.yaxis2.title.text = r\"Survival rate\"\n", " figure.layout.xaxis3.title.text = r\"Clutch size\"\n", " figure.layout.yaxis3.title.text = r\"Surviving offspring\"\n", " figure.layout.showlegend = False\n", " figure.add(plotly_text(\"Survival rate
(fraction of offspring that survive)\", (0.22,0.42), \n", " font_size=18, paper=True, xanchor=\"center\", yanchor=\"top\"))\n", " color1 = plotly.colors.sequential.Viridis[6]\n", " color2 = plotly.colors.sequential.Viridis[0]\n", " color3 = plotly.colors.sequential.Viridis[9]\n", " labels = [\"Don't survive\", \"Survive\"]\n", " survival_chart = plotly.graph_objects.Pie(labels=labels, sort=False, \n", " marker_colors=[color2, color1], textinfo=\"label+percent\")\n", " survival_chart.domain = {\"x\": [0, 0.45], \"y\": [0.44, 0.95]}\n", " survival_chart = figure.add(survival_chart)\n", " survival_graph = plotly_function(survival, (x, 0, 9), color=color1)\n", " survival_graph = figure.add(survival_graph, subplot=(2,1))\n", " labels = [\"Clutch size\", \"Offspring
that survive\"]\n", " offspring_chart = plotly.graph_objects.Bar(x=labels, orientation=\"v\", \n", " marker_color=[color3, color1])\n", " offspring_chart = figure.add(offspring_chart, subplot=(1,2))\n", " netoffspring_graph = plotly_function(x, (x, 0, 1), color=color1)\n", " netoffspring_graph = figure.add(netoffspring_graph, subplot=(2,2))\n", " maximum = find_root(diff(x * survival(x), x), 1, 8)\n", " maxline = plotly_lines([(maximum, -1), (maximum, 2)], color=color2, line_dash=\"dash\")\n", " maxline = figure.add(maxline, subplot=(2, 2))\n", " maxlabel = plotly_text(fr\"{int(maximum)} eggs\", \n", " (maximum - 0.1, 0.7), font_size=14, xanchor=\"right\", xref=\"x3\", yref=\"y3\")\n", " maxlabel = figure.add(maxlabel)\n", "\n", " @interact(C=slider(0, 9, default=0, label=\"Clutch size\"), \n", " show_graph=checkbox(False, label=\"Show graph\"), \n", " show_max=checkbox(False, label=\"Show maximum\"))\n", " def update(C, show_graph, show_max):\n", " survival_pie = np.array([1 - survival(C), survival(C)], dtype=float)\n", " offspring = np.array([C, C*survival(C)], dtype=float)\n", " netoffspring_update = plotly_function(x*survival(x), (x, 0, C), update=True)\n", " with figure.batch_update():\n", " survival_chart.update(values=survival_pie)\n", " offspring_chart.update(y=offspring)\n", " netoffspring_graph.update(netoffspring_update)\n", " figure.layout.xaxis2.visible = show_graph\n", " figure.layout.yaxis2.visible = show_graph\n", " survival_graph.visible = show_graph\n", " figure.layout.xaxis3.visible = show_graph\n", " figure.layout.yaxis3.visible = show_graph\n", " netoffspring_graph.visible = show_graph\n", " maxline.visible = show_graph and show_max and C > maximum\n", " maxlabel.visible = show_graph and show_max and C > maximum\n", " if show_graph:\n", " figure.layout.yaxis1.domain = [0.42, 1]\n", " figure.layout.yaxis3.domain = [0, 0.28]\n", " else:\n", " figure.layout.yaxis1.domain = [0.1, 1]\n", " figure.layout.yaxis3.domain = [0, 0.1]\n", "\n", " return figure\n", "\n", "clutchsize_interactive()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "source": [ "## Example 3: Optimal vascular branching\n", "
\n", "\n", "Major arteries branch off into smaller arteries, which branch into arterioles, then smaller arterioles, etc, on down to the level of capillaries, the thinnest blood vessels. \n", "\n", "- In each artery, there is some ***vascular resistance***: essentially friction that pushes against the blood flowing through the vessels. \n", "- In thinner arteries, the resistance is *much* higher per unit of length, so it's advantageous to keep these thinner arteries shorter. \n", "- But blood still needs to reach tissues that are not directly along the wider arteries. Making the thinner arteries as short as possible requires making the wider arteries longer, resulting in more vascular resistance in the wider artery. \n", "\n", "What should be optimized here? \n", "
\n", "
\n" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "65255613d11341aebfb474e759edf081", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Interactive function .update at 0x7f01cfc64ca0> with 3 widgets\n", " theta:…" ] }, "execution_count": 9, "metadata": { }, "output_type": "execute_result" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "05245ed3d25349e0995a5afafb7b8eea", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FigureWidget({\n", " 'data': [{'line': {'color': 'darkred', 'width': 60},\n", " 'mode': 'lines',\n", " …" ] }, "execution_count": 9, "metadata": { }, "output_type": "execute_result" } ], "source": [ "def vascular_interactive():\n", " k = 1e7\n", " r1 = 60\n", " r2 = 48\n", " d = 1.6\n", " p = 0.3\n", " distance(x) = d - p * cot(x*pi/180)\n", " resistance1(x) = k/r1^4 * distance(x)\n", " resistance2(x) = k/r2^4 * p * csc(x*pi/180)\n", " resistance = resistance1 + resistance2\n", "\n", " figure = FigureWidget(subplots=[[{\"colspan\": 2}, None], [{}, {}]])\n", " figure.layout.margin = dict(t=5, l=60)\n", " figure.axes_ranges((0, 1.4*d), (-0.3*p, 1.35*p), scale=(1,1))\n", " figure.layout.xaxis1.visible = False\n", " figure.layout.yaxis1.visible = False\n", " figure.layout.yaxis2.range = [0, 2.5]\n", " figure.layout.xaxis3.range = [15, 90]\n", " figure.layout.yaxis3.range = [1.6, 2.5]\n", " figure.layout.yaxis2.title.text = r\"Resistance (HRU)\"\n", " figure.layout.xaxis3.title.text = r\"Branching angle (degrees)\"\n", " figure.layout.yaxis3.title.text = r\"Resistance (HRU)\"\n", " figure.layout.showlegend = False\n", " figure.add(plotly_text(\"The smaller blood vessel is 80% as wide as the main artery.\", \n", " (0, 0.90), paper=True, font_size=14))\n", " figure.add(plotly_text(\"Vascular resistance\", (0.22, 0.44), paper=True, \n", " xanchor=\"center\", font_size=18))\n", " title = plotly_text(\"Vascular resistance\", (0.72, 0.44), paper=True, \n", " xanchor=\"center\", font_size=18)\n", " color1 = plotly.colors.sequential.Viridis[6]\n", " color2 = plotly.colors.sequential.Viridis[9]\n", " color3 = plotly.colors.sequential.Viridis[0]\n", " figure.add(plotly_lines([(0,0), (2*d,0)], line_width=r1, color=\"darkred\"))\n", " figure.add(plotly_text(\"Main artery\", (0.15*d, 0.15*p), color=\"white\", font_size=16))\n", " labels = [\"Main artery
to branch point\", \"Smaller blood
vessel\", \"Total
resistance\"]\n", " resistance_chart = plotly.graph_objects.Bar(x=labels, orientation=\"v\", \n", " marker_color=[color1, color2, color3])\n", " resistance_chart = figure.add(resistance_chart, subplot=(2,1))\n", " resistance_graph = plotly_function(x, (x, 0, 1), color=color3)\n", " resistance_graph = figure.add(resistance_graph, subplot=(2,2))\n", " tissue(t) = (d*(1 + 0.06*cos(t) + 0.01*cos(11*t)), p*(1.05 + 0.24*sin(t) + 0.04*sin(11*t)))\n", " tissue = plotly_parametric(tissue, (t, 0, 2*pi), fill=\"toself\", color=color3, fillcolor=color3)\n", " figure.add(plotly_text(\"Tissue\", (d, 1.05*p), color=\"white\", font_size=16))\n", " label = plotly_text(\"Smaller
artery\", (0,0), color=\"white\", font_size=16)\n", " minimum = arccos((r2 / r1)^4) * 180 / pi\n", " minline = plotly_lines([(minimum, 0), (minimum, 3)], color=color1, line_dash=\"dash\")\n", " minline = figure.add(minline, subplot=(2, 2))\n", " minlabel = plotly_text(fr\"$\\theta = {round(minimum, 2)}^\\circ$\", \n", " (minimum - 0.5, 2.3), font_size=14, xanchor=\"right\", xref=\"x3\", yref=\"y3\")\n", " title, label, minlabel = figure.add(title, label, minlabel)\n", "\n", " @interact(theta=slider(15, 89, default=15, label=\"Branch angle\"), \n", " show_graph=checkbox(False, label=\"Show graph\"), \n", " show_min=checkbox(False, label=\"Show minimum\"))\n", " def update(theta, show_graph, show_min):\n", " initialized = figure.initialized()\n", " values = np.array([resistance1(theta), resistance2(theta), resistance(theta)], dtype=float)\n", " graph_update = plotly_function(resistance1(x) + resistance2(x), (x, 10, theta), update=True)\n", " branch = plotly_lines([(distance(theta),0), (d,p)], line_width=r2, \n", " color=\"darkred\", update=initialized)\n", " tobranchpoint = plotly_lines([(0,0), (distance(theta),0)], line_width=2, \n", " color=color1, update=initialized)\n", " frombranchpoint = plotly_lines([(distance(theta),0), (d,p)], line_width=2, \n", " color=color2, update=initialized)\n", " with figure.batch_update():\n", " figure.auto_update(branch, tobranchpoint, frombranchpoint, tissue)\n", " label.update(x=float(0.5*distance(theta) + 0.5*d), y=float(0.5*p), textangle=-theta)\n", " resistance_chart.update(y=values)\n", " resistance_graph.update(graph_update)\n", " figure.layout.xaxis3.visible = show_graph\n", " figure.layout.yaxis3.visible = show_graph\n", " resistance_graph.visible = show_graph\n", " title.visible = show_graph\n", " minline.visible = show_graph and show_min\n", " minlabel.visible = show_graph and show_min\n", "\n", " return figure\n", "\n", "vascular_interactive()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "source": [ "## What's the biological mechanism underlying all of these? \n", "
\n", "\n", "The bird doesn't do a calculus problem to decide how large its foraging radius should be, or how many eggs it should lay. \n", "\n", "The cells don't solve math problems when arranging themselves to form arteries, in order to minimize the total vascular resistance. \n", "\n", "So what's actually behind all this? \n", "
\n" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "slideshow": { "slide_type": "fragment" } }, "source": [ "
\n", "\n", "**Evolution!**\n", "\n", "More precisely, natural selection: survival of the ***fittest***. \n", "
\n", "
\n" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "4527a4e64b494dee984a555abf007e5e", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Interactive function .update at 0x7f01cfc610d0> with 2 widgets\n", " l: Tran…" ] }, "execution_count": 10, "metadata": { }, "output_type": "execute_result" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "110416b561bd488883930f017a37aa74", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FigureWidget({\n", " 'data': [{'line': {'color': '#35b779', 'shape': 'spline', 'smoothing': 1.30000000000000},\n", " …" ] }, "execution_count": 10, "metadata": { }, "output_type": "execute_result" } ], "source": [ "def fitness_interactive():\n", " fitness(x) = 0.79 / (1 + ((x - 60)/30)^2) * (1 + (x - 60)/100)\n", "\n", " figure = FigureWidget(subplots=[{}, {}])\n", " figure.axes_ranges((0, 100), (0, 60), scale=(1,1))\n", " figure.axes_labels(\"Body length (cm)\", \"\")\n", " figure.layout.margin.r = 100\n", " figure.layout.xaxis1.zeroline = False\n", " figure.layout.yaxis1.visible = False\n", " figure.layout.xaxis2.range = [0, 100]\n", " figure.layout.yaxis2.range = [0, 1]\n", " figure.layout.xaxis2.title.text = \"Body length (cm)\"\n", " figure.layout.yaxis2.title.text = \"Fitness\"\n", " figure.layout.showlegend = False\n", " figure.add(plotly_text(\"Fitness graph\", (0.78,0.95), font_size=18, paper=True, xanchor=\"center\"))\n", " figure.add_layout_image(source=\"images/FishImage.png\", x=0, y=0, \n", " xref=\"x\", yref=\"y\", xanchor=\"left\", yanchor=\"bottom\")\n", " fish = figure.layout.images[0]\n", " color1 = plotly.colors.sequential.Viridis[6]\n", " color2 = plotly.colors.sequential.Viridis[0]\n", " figure.add(plotly_function(fitness, (x, 5, 100), color=color1), subplot=2)\n", " point_on_graph = figure.add(plotly_points([(0, 0)]), subplot=2)\n", " maximum = find_root(fitness.derivative(), 10, 100)\n", " maxline = plotly_lines([(maximum, -0.1), (maximum, 0.9)], color=color2, line_dash=\"dash\")\n", " maxline = figure.add(maxline, subplot=2)\n", " maxlabel = plotly_text(fr\"{round(maximum, 1)} cm\", \n", " (maximum - 0.5, 0.4), font_size=14, xanchor=\"right\", xref=\"x2\", yref=\"y2\")\n", " maxlabel = figure.add(maxlabel)\n", "\n", " @interact(l=slider(5, 100, 1, default=20, label=\"Body length\"), \n", " show_max=checkbox(False, label=\"Show maximum\"))\n", " def update(l, show_max):\n", " with figure.batch_update():\n", " fish.update(sizex=l, sizey=l)\n", " point_on_graph.update(x=[float(l)], y=[float(fitness(l))])\n", " maxline.visible = show_max\n", " maxlabel.visible = show_max\n", "\n", " return figure\n", "\n", "fitness_interactive()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "source": [ "## Fitness is a function of genotype\n", "
\n", "\n", "(or phenotype)\n", "\n", "**Definition:** The ***fitness*** of a particular genotype is the *average number* (expected value) of offspring that an individual with that exact genotype will have. \n", "\n", "In other words, for any combination of genes, fitness measures how much an individual with those genes is likely to contribute to the gene pool of the next generation. \n", "
\n" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "source": [ "## Example 4: Darwin's finches\n", "\n", "\"Four\n" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": false, "slideshow": { "slide_type": "skip" } }, "outputs": [ ], "source": [ "finches = {\n", " (0.85, 0.15): \"images/DarwinsFinches1.png\", \n", " (0.40, 0.25): \"images/DarwinsFinches2.png\", \n", " (0.15, 0.65): \"images/DarwinsFinches3.png\", \n", " (0.50, 0.85): \"images/DarwinsFinches4.png\", \n", "}\n", "var(\"x, y\")\n", "bump(x0, y0, h) = h^2/(h^2 + (x - x0)^2 + (y - y0)^2)\n", "fitness(x, y) = 0\n", "genotypes = np.array(list(finches.keys()), dtype=float)\n", "for (x0, y0), a in zip(genotypes, (0.7, 0.3, 0.35, 0.6)):\n", " fitness += a * bump(x0, y0, 0.15 + 0.08*random())\n", "gradient = fitness.gradient()\n", "hessian = jacobian(gradient, (x, y))\n", "crit_points = find_zeros(gradient, (x, 0, 1), (y, 0, 1))\n", "crit_points = [pt for pt in crit_points if all(l < 0 for l in hessian(*pt).eigenvalues())]\n", "closest = lambda pt: np.linalg.norm(genotypes - pt, axis=1).argmin()\n", "crit_points.sort(key=closest)\n", "for oldpt, newpt in zip(genotypes, crit_points):\n", " finches[tuple(newpt)] = finches.pop(tuple(oldpt))" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "f4bc597c6c3f4554bc4245821792a41e", "version_major": 2, "version_minor": 0 }, "text/plain": [ "interactive(children=(SelectionSlider(description='Finches', options=('None', 1, 2, 3, 4, 'All'), value='None'…" ] }, "execution_count": 15, "metadata": { }, "output_type": "execute_result" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "095b0f792fa64b6c8ba8cf267627051f", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FigureWidget({\n", " 'data': [{'colorscale': [[0.0, 'lightblue'], [1.0, 'lightblue']],\n", " 'opacity': …" ] }, "execution_count": 15, "metadata": { }, "output_type": "execute_result" } ], "source": [ "def finches_interactive():\n", " figure = FigureWidget(subplots=[{}, {\"type\": \"scene\"}])\n", " figure.layout.yaxis.domain = [0, 0.95]\n", " figure.layout.scene.domain.y = [0, 0.95]\n", " figure.axes_ranges((0, 1), (0, 1), scale=(1,1))\n", " figure.axes_labels(\"Beak length\", \"Beak pointiness\")\n", " figure.axes_ranges((0, 1), (0, 1), (0, 1), scale=(1,1,1))\n", " figure.axes_labels(\"Beak length\", \"Beak pointiness\", \"Fitness\")\n", " figure.layout.showlegend = False\n", " figure.add(plotly_text(\"Genotype space\", (0.22,0.95), font_size=18, \n", " paper=True, xanchor=\"center\"))\n", " title = figure.add(plotly_text(\"Fitness landscape\", (0.78,0.95), font_size=18, \n", " paper=True, xanchor=\"center\"))\n", " for (x0, y0), source in finches.items():\n", " figure.add_layout_image(source=source, x=x0, y=y0, sizex=0.2, sizey=0.2, \n", " xref=\"x\", yref=\"y\", xanchor=\"center\", yanchor=\"middle\")\n", " figure.add_layout_image(source=source, x=0.5, y=0.5, sizex=0.5, sizey=1, \n", " xref=\"paper\", yref=\"paper\", xanchor=\"left\", yanchor=\"middle\")\n", " fitness_landscape = figure.add(plotly_function3d(fitness, (x, 0, 1), (y, 0, 1), opacity=0.8))\n", " maxima = [(x0, y0, fitness(x0, y0)) for x0, y0 in finches]\n", " maxima = figure.add(plotly_points3d(maxima, size=1.5, color=\"darkgreen\"))\n", "\n", " @interact(finch=slider([\"None\", 1, 2, 3, 4, \"All\"], label=\"Finches\"), \n", " show_graph=checkbox(False, label=\"Show fitness landscape\"), \n", " show_maxima=checkbox(False, label=\"Show species\"))\n", " def update(finch, show_graph, show_maxima):\n", " finchnum = -1 if finch == \"None\" else 99 if finch == \"All\" else finch - 1\n", " with figure.batch_update():\n", " for i in range(4):\n", " figure.layout.images[2*i].visible = (i < finchnum)\n", " figure.layout.images[2*i + 1].visible = (i == finchnum)\n", " title.visible = show_graph\n", " fitness_landscape.visible = show_graph\n", " maxima.visible = show_maxima\n", "\n", " return figure\n", "\n", "finches_interactive()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "source": [ "## Local maxima and local minima\n", "
\n", "\n", "**Definition:** \n", "- A function has a ***local maximum*** at a point $x$ if there is some region around $x$ such that, *within that region,* the maximum value of the function occurs at $x$. \n", "- A function has a ***local minimum*** at a point $x$ if there is some region around $x$ such that, *within that region,* the minimum value of the function occurs at $x$. \n", "\n", "Also, maxima and minima collectively are called *extrema*, as in the *extreme values* of a function. \n", "
\n" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/html": "\n\n\n
\n
\n\n" }, "execution_count": 16, "metadata": { }, "output_type": "execute_result" } ], "source": [ "f(x) = (16*(x-3)^4 - 10*(x-3)^2 + 3*(x-3) + 4) * exp(-(x-3)^2)\n", "df = f.derivative()\n", "d2f = df.derivative()\n", "critpts = find_zeros1d(df, (x, 0, 6))\n", "colors = []\n", "for x0 in critpts:\n", " evalue = d2f(x0)\n", " colors.append(\"darkgreen\" if evalue < 0 else \"darkred\")\n", "critpts = [(x0, f(x0)) for x0 in critpts]\n", "\n", "figure = Figure()\n", "figure.axes_ranges((0, 6), (0, 8))\n", "figure.axes_labels(\"$x$\", \"$f(x)$\")\n", "figure.layout.margin.b = 80\n", "figure.layout.margin.r = 200\n", "figure.add(plotly_function(f, (x, 0, 6), name=\"Graph of function\", plotpoints=161))\n", "figure.add(plotly_points(critpts, color=colors, name=\"Extrema\", visible=\"legendonly\"))\n", "figure" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "source": [ "# Conclusions: \n", "
\n", "\n", "1. Many many features (anatomical, behavioral, even biochemical) in biology seem to be the result of an optimization process. Any time one can say a species is “adapted to do ... very well”, that's probably describing such a result. \n", "\n", "2. The biological mechanism underlying all of these optimizations is **evolution**, or more specifically, natural selection: survival of the *fittest*. The ***fitness*** of a certain genotype (or phenotype) is defined roughly as the average number of offspring an individual with that genotype will produce. \n", "\n", "3. A function has a ***local maximum*** at a point $x$ if there is some region around $x$ such that, within that region, the maximum value of the function occurs at $x$. A ***local minimum*** is defined similarly. \n", "\n", "4. One mathematical model for *speciation* is that species can form at any one of the local maxima of the fitness function, or ***fitness landscape***. \n", "
\n", "
\n" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "collapsed": false, "slideshow": { "slide_type": "skip" } }, "outputs": [ ], "source": [ ] } ], "metadata": { "celltoolbar": "Slideshow", "hide_input": false, "kernelspec": { "argv": [ "sage-9.8", "--python", "-m", "sage.repl.ipython_kernel", "--matplotlib=inline", "-f", "{connection_file}" ], "display_name": "SageMath 9.8", "env": { }, "language": "sagemath", "metadata": { "cocalc": { "description": "Open-source mathematical software system", "priority": 1, "url": "https://www.sagemath.org/" } }, "name": "sage-9.8", "resource_dir": "/ext/jupyter/kernels/sage-9.8" }, "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.9.2" } }, "nbformat": 4, "nbformat_minor": 4 }