{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "# N(utrient)-P(hytoplankton)-Z(ooplankton)-D(etritus) Model\n", "
\n", "### A toy interactive model of ocean ecosystem dynamics\n", "\n", "
\n", "Riley X. Brady\n", "\n", "
\n", "riley.brady@colorado.edu\n", "\n", "---\n", "
\n", "**References:**\n", "\n", "1. J.L. Sarmiento and N. Gruber (2006). Ocean Biogeochemical Dynamics. Chapter 4: \"Production.\"\n", "\n", "1. A.M. Edwards (2001). Adding Detritus to a Nutrient–Phytoplankton–Zooplankton Model :A Dynamical-Systems Approach. J Plankton Res.\n", "\n", "\n", "---\n", "## Summary\n", "We can create a reduced model of a complex lower-trophic ocean ecosystem with a few differential equations. Here, we choose to model four variables: an arbitrary nutrient concentration (generally thought of as a macronutrient such as nitrate), a phytoplankton concentration (maybe a diatom), a zooplankton concentration (with option to change the type of zooplankter), and a detritus concentration (waste products).\n", "\n", "In reality, we are just modeling a finite reservoir of nitrate, and considering how it gets redistributed around the ecosystem, given a few initial conditions and parameter settings. In other words, we aren't explicitly modeling phytoplankton cell count or biomass, but rather tracking where the nitrate goes as it is incorporated into organic matter via photosynthesis, or consumed by zooplankton.\n", "\n", "Differential equations (DE's) are complex things to deal with. In a model like this, we have four DE's interacting with one another, because the rate of change of the given population (nutrient, phytoplankton, zooplankton, or detritus) is dependent on the current state of the other three populations. Thus, it is a lot easier to discretize a model into time steps, and reduce our DE's into algebraic equations that may be solved in reference to the current state of the system.\n", "\n", "Here, I use an explicit time-differencing scheme (forward Euler method) to model this simple ocean ecosystem.\n", "\n", "---\n", "\n", "## Differential Equations Contributing to the Model\n", "The four raw DE's are as follows (where N is our nutrients, P is our phytoplankton, Z is our zooplankton, and D is our detritus):\n", "\n", "$\\frac{dN}{dt} = -V_{m}\\left(\\frac{N}{K_{N}+N}\\right)f(I_{0})P~+~\\alpha R_{m}\\left(1-e^{-\\lambda P}\\right)Z~+~\\epsilon P~+~gZ~+~\\phi D$\n", "\n", "$\\frac{dP}{dt} = V_{m}\\left(\\frac{N}{K_{N} + N}\\right)f(I_{0})P~-~R_{m}\\left(1 - e^{-\\lambda P}\\right)Z~-~\\epsilon P~-~rP$\n", "\n", "$\\frac{dZ}{dt} = \\beta R_{m}\\left(1-e^{-\\lambda P}\\right)Z~-~gZ$\n", "\n", "$\\frac{dD}{dt} = rP~+~(1 - \\alpha - \\beta)R_{m}\\left(1 - e^{-\\lambda P}\\right)Z~-~\\phi D$\n", "\n", "---\n", "\n", "### Terms\n", "#### Bulk Terms\n", "If you look closely at each DE, you note that these are simply source (+) minus sink (-) equations. This simple model only has a few nitrogen exchange processes: \n", "\n", "$V_{m}\\left(\\frac{N}{K_{N} + N}\\right)f(I_{0})P$ : Phytoplankton grazing term. How much inorganic nitrogen are they taking up?\n", "\n", "$R_{m}\\left(1-e^{-\\lambda P}\\right)Z$ : Zooplankton grazing term. How much nitrogen are they taking up after consuming phytoplankton and releasing some as waste? The $\\beta$ coefficient represents the proportion taken up into zooplankton organic matter; the $\\alpha$ coefficient is that which is dissolved back into nutrients (perhaps from urine); and (1-$\\alpha$-$\\beta$) is that which is excreted as fecal pellets (to the detritus compartment). \n", "\n", "$\\epsilon P$ : How much nitrogen is being returned to the pool from phytoplankton death?\n", "\n", "$gZ$ : How much nitrogen is being returned to the pool from zooplankton death?\n", "\n", "$rP$ : How much nitrogen is being respired by phytoplankton into detritus?\n", "\n", "#### Phytoplankton Terms\n", "$V_{m}$ : Maximum growth rate of an individual plankter (div per day). This value is dependent on temperature, via a lab-derived equation for diatoms: $V_{m} = a\\cdot b^{T}$, with $a=0.6d^{-1}$, $b=1.066$, and $c=1(degC)^{-1}$.\n", "\n", "$K_{N}$ : Half-saturation constant for nitrogen uptake ($\\mu molNl^{-1}$). This is the nitrogen concentration at which the phytoplankton growth rate is at half its maximum value.\n", "\n", "$f_{0}$ : Light intensity (0 to 1 weighting function). This is a simple parameterization of a more complex hyperbolic term that uses a similar term to $K_{N}$.\n", "\n", "$\\epsilon$ : Phytoplankton death rate (cells per day).\n", "\n", "$r$ : Respiration rate (per day).\n", "\n", "#### Zooplankton Terms\n", "$R_{m}$ : Maximum grazing rate of zooplankton on phytoplankton (cells per day).\n", "\n", "$\\lambda$ : Grazing constant ($\\mu molNl^{-1}$).\n", "\n", "$\\beta$ : Proportion of assimilated nitrogen by zooplankton. In other words, when they graze upon a phytoplankter, how efficient are they at taking up the nitrogen? (dimensionless)\n", "\n", "$\\alpha$ : Proportion of nitrogen taken up by zooplankton that returns to the environment as dissolved nutrients (urine?).\n", "\n", "$g$ : Zooplankton death rate (critters per day).\n", "\n", "#### Detritus Terms\n", "$\\phi$ : The remineralization rate of detritus back into dissolved nutrients (per day).\n", "\n", "---\n", "### Fixed Values/Initial Conditions\n", "\n", "| Parameter | Symbol | Default Value |\n", "|:----------------------------------------:|:----------:|:-----------------:|\n", "| Ambient Temperature | T | 15 degC |\n", "| Half-saturation constant for $N$ uptake | $K_{N}$ | 1$\\mu$mol per L |\n", "| Maximum Grazing Rate | R$_{m}$ | 1 $d^{-1}$ |\n", "| Zooplankton Death Rate | g | 0.2$d^{-1}$ |\n", "| Zooplankton Grazing Constant | $\\lambda$ | 0.2$\\mu$mol per L |\n", "| Phytoplankton Death Rate | $\\epsilon$ | 0.1$d^{-1}$ |\n", "| Proportional Light Intensity | f$_{0}$ | 0.25 |\n", "| Zooplankton Dissolved Excretion Fraction | $\\alpha$ | 0.3 |\n", "| Zooplankton Assimilation Efficiency | $\\beta$ | 0.6 |\n", "| Phytoplankton Respiration Rate | r | 0.15 |\n", "| Detritus Remineralization Rate | $\\phi$ | 0.4 $d^{-1}$ |\n", "\n", "---\n", "\n", "### Tools\n", "This model was built using Python 3 and visualized using [Bokeh](http://bokeh.pydata.org/en/latest/).\n" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Outside packages\n", "import numpy as np\n", "\n", "# Bokeh packages\n", "from bokeh.io import output_notebook, show, gridplot, output_file, save\n", "from bokeh.layouts import column, widgetbox\n", "from bokeh.models import CustomJS, ColumnDataSource, Slider, FixedTicker\n", "from bokeh.models.widgets import Slider, Dropdown, RadioButtonGroup\n", "from bokeh.plotting import figure\n", "from bokeh.charts import Area, Bar\n", "\n", "# Set up colors (from ColorBrewer discrete colors)\n", "cmap = [\"#bebada\", \"#8dd3c7\", \"#fb8072\", \"#e5d8bd\"]" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "\n", "
\n", " \n", " Loading BokehJS ...\n", "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/javascript": [ "\n", "(function(global) {\n", " function now() {\n", " return new Date();\n", " }\n", "\n", " var force = true;\n", "\n", " if (typeof (window._bokeh_onload_callbacks) === \"undefined\" || force === true) {\n", " window._bokeh_onload_callbacks = [];\n", " window._bokeh_is_loading = undefined;\n", " }\n", "\n", "\n", " \n", " if (typeof (window._bokeh_timeout) === \"undefined\" || force === true) {\n", " window._bokeh_timeout = Date.now() + 5000;\n", " window._bokeh_failed_load = false;\n", " }\n", "\n", " var NB_LOAD_WARNING = {'data': {'text/html':\n", " \"
\\n\"+\n", " \"

\\n\"+\n", " \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+\n", " \"may be due to a slow or bad network connection. Possible fixes:\\n\"+\n", " \"

\\n\"+\n", " \"
    \\n\"+\n", " \"
  • re-rerun `output_notebook()` to attempt to load from CDN again, or
  • \\n\"+\n", " \"
  • use INLINE resources instead, as so:
  • \\n\"+\n", " \"
\\n\"+\n", " \"\\n\"+\n", " \"from bokeh.resources import INLINE\\n\"+\n", " \"output_notebook(resources=INLINE)\\n\"+\n", " \"\\n\"+\n", " \"
\"}};\n", "\n", " function display_loaded() {\n", " if (window.Bokeh !== undefined) {\n", " document.getElementById(\"88e5714e-5a69-4482-9094-a3a04ad5f72a\").textContent = \"BokehJS successfully loaded.\";\n", " } else if (Date.now() < window._bokeh_timeout) {\n", " setTimeout(display_loaded, 100)\n", " }\n", " }\n", "\n", " function run_callbacks() {\n", " window._bokeh_onload_callbacks.forEach(function(callback) { callback() });\n", " delete window._bokeh_onload_callbacks\n", " console.info(\"Bokeh: all callbacks have finished\");\n", " }\n", "\n", " function load_libs(js_urls, callback) {\n", " window._bokeh_onload_callbacks.push(callback);\n", " if (window._bokeh_is_loading > 0) {\n", " console.log(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n", " return null;\n", " }\n", " if (js_urls == null || js_urls.length === 0) {\n", " run_callbacks();\n", " return null;\n", " }\n", " console.log(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n", " window._bokeh_is_loading = js_urls.length;\n", " for (var i = 0; i < js_urls.length; i++) {\n", " var url = js_urls[i];\n", " var s = document.createElement('script');\n", " s.src = url;\n", " s.async = false;\n", " s.onreadystatechange = s.onload = function() {\n", " window._bokeh_is_loading--;\n", " if (window._bokeh_is_loading === 0) {\n", " console.log(\"Bokeh: all BokehJS libraries loaded\");\n", " run_callbacks()\n", " }\n", " };\n", " s.onerror = function() {\n", " console.warn(\"failed to load library \" + url);\n", " };\n", " console.log(\"Bokeh: injecting script tag for BokehJS library: \", url);\n", " document.getElementsByTagName(\"head\")[0].appendChild(s);\n", " }\n", " };var element = document.getElementById(\"88e5714e-5a69-4482-9094-a3a04ad5f72a\");\n", " if (element == null) {\n", " console.log(\"Bokeh: ERROR: autoload.js configured with elementid '88e5714e-5a69-4482-9094-a3a04ad5f72a' but no matching script tag was found. \")\n", " return false;\n", " }\n", "\n", " var js_urls = [\"https://cdn.pydata.org/bokeh/release/bokeh-0.12.4.min.js\", \"https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.4.min.js\"];\n", "\n", " var inline_js = [\n", " function(Bokeh) {\n", " Bokeh.set_log_level(\"info\");\n", " },\n", " \n", " function(Bokeh) {\n", " \n", " document.getElementById(\"88e5714e-5a69-4482-9094-a3a04ad5f72a\").textContent = \"BokehJS is loading...\";\n", " },\n", " function(Bokeh) {\n", " console.log(\"Bokeh: injecting CSS: https://cdn.pydata.org/bokeh/release/bokeh-0.12.4.min.css\");\n", " Bokeh.embed.inject_css(\"https://cdn.pydata.org/bokeh/release/bokeh-0.12.4.min.css\");\n", " console.log(\"Bokeh: injecting CSS: https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.4.min.css\");\n", " Bokeh.embed.inject_css(\"https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.4.min.css\");\n", " }\n", " ];\n", "\n", " function run_inline_js() {\n", " \n", " if ((window.Bokeh !== undefined) || (force === true)) {\n", " for (var i = 0; i < inline_js.length; i++) {\n", " inline_js[i](window.Bokeh);\n", " }if (force === true) {\n", " display_loaded();\n", " }} else if (Date.now() < window._bokeh_timeout) {\n", " setTimeout(run_inline_js, 100);\n", " } else if (!window._bokeh_failed_load) {\n", " console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n", " window._bokeh_failed_load = true;\n", " } else if (force !== true) {\n", " var cell = $(document.getElementById(\"88e5714e-5a69-4482-9094-a3a04ad5f72a\")).parents('.cell').data().cell;\n", " cell.output_area.append_execute_result(NB_LOAD_WARNING)\n", " }\n", "\n", " }\n", "\n", " if (window._bokeh_is_loading === 0) {\n", " console.log(\"Bokeh: BokehJS loaded, going straight to plotting\");\n", " run_inline_js();\n", " } else {\n", " load_libs(js_urls, function() {\n", " console.log(\"Bokeh: BokehJS plotting callback run at\", now());\n", " run_inline_js();\n", " });\n", " }\n", "}(this));" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Allow Bokeh to be utilized inline with Jupyter.\n", "output_notebook()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Default Model View\n", "\n", "---\n", "We first need to compute the model in Python with some basic parameterizations that we know work. This will serve as the default view when the user opens the page." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Here we set up the default parameters/coefficients. \n", "DT = 1 # Time Step (in days)\n", "NUM_STEPS = 150 # Number of time steps to be computed and plotted\n", "\n", "# Temperature-Dependent Growth Rate\n", "a = 0.6\n", "b = 1.066\n", "c = 1\n", "T = 15\n", "Vm = a * b**(c*T) # Maximum growth rate (per day)\n", "\n", "# Other parameters\n", "Kn = 1 # Half-saturation constant for nitrogen uptake (umolN per l)\n", "Rm = 1 # Maximum grazing rate (per day)\n", "g = 0.2 # Zooplankton death rate (per day)\n", "lambda_Z = 0.2 # Grazing constant (umolN per l)\n", "epsilon = 0.1 # Phyto death rate (per day)\n", "f = 0.25 # Light intensity (assumed constant)\n", "\n", "# Detritus-related stuff.\n", "alpha = 0.3 # Fraction of zoo. uptake that goes immediately to dissolved nutrients.\n", "beta = 0.6 # Assimilation efficiency of zooplankton.\n", "r = 0.15 # Respiration rate.\n", "phi = 0.4 # Remineralization rate of detritus." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Set Initial Conditions (umol per L)\n", "N_0 = 4 \n", "P_0 = 2.5 \n", "Z_0 = 1.5\n", "D_0 = 0" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Initialize Arrays\n", "N = np.empty(NUM_STEPS, dtype=\"float\")\n", "P = np.empty(NUM_STEPS, dtype=\"float\")\n", "Z = np.empty(NUM_STEPS, dtype=\"float\")\n", "D = np.empty(NUM_STEPS, dtype=\"float\")\n", "\n", "# Insert Initial Values\n", "N[0] = N_0\n", "P[0] = P_0\n", "Z[0] = Z_0\n", "D[0] = D_0" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Compute Simulation in Python" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Here we use the Euler forward method to solve for t+1 and reference t. \n", "for idx in np.arange(1, NUM_STEPS, 1):\n", " t = idx - 1\n", " \n", " # Common terms for simpler code\n", " gamma_N = N[t] / (Kn + N[t])\n", " zoo_graze = Rm * (1 - np.exp(-lambda_Z * P[t])) * Z[t]\n", " \n", " # Equation calculations\n", " N[idx] = DT * (-Vm*gamma_N*f*P[t] + alpha*zoo_graze + epsilon*P[t] + g*Z[t] + phi*D[t]) + N[t] \n", " P[idx] = DT * (Vm*gamma_N*f*P[t] - zoo_graze - epsilon*P[t] - r*P[t]) + P[t]\n", " Z[idx] = DT * (beta*zoo_graze - g*Z[t]) + Z[t] \n", " D[idx] = DT * (r*P[t] + (1-alpha-beta)*zoo_graze - phi*D[t]) + D[t]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Set up Bokeh Data Structure" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [], "source": [ "x = np.arange(1, NUM_STEPS + 1, 1)\n", "N = N\n", "P = P\n", "Z = Z\n", "D = D\n", "\n", "# Bokeh likes reading data via its own version of dictionaries.\n", "# I also prime this dictionary with additional variables for \n", "# multiple plots, which makes the custom JS interaction a lot easier to manage.\n", "source = ColumnDataSource(data = {\n", " 'x' : x,\n", " 'N' : N,\n", " 'P' : P,\n", " 'Z' : Z,\n", " 'D' : D,\n", " 'Psum' : N + P, # These sum variables can be removed... are used for a stacked bar plot.\n", " 'Zsum' : N + P + Z,\n", " 'Dsum' : N + P + Z + D,\n", " })" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Functions for plotting\n", "def plotlines(plot, x, y, source, legend, line_width=3, line_alpha=0.75,\n", " color='black'):\n", " plot.line(x, y, source=source, line_width=line_width,\n", " line_alpha=line_alpha, color=color, legend=legend)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Standard Visualization (before interaction)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# TIME SERIES PLOT\n", "plot = figure(plot_width=900, plot_height=300,\n", " toolbar_location=\"right\", tools = \"save\",\n", " x_range=(1, NUM_STEPS + 1), title=\"N-P-Z-D Time Series\",\n", " webgl=True)\n", "\n", "# Plot data\n", "plotlines(plot, 'x', 'N', source, \"Nutrients\", color=cmap[0])\n", "plotlines(plot, 'x', 'P', source, \"Phytoplankton\", color=cmap[1])\n", "plotlines(plot, 'x', 'Z', source, \"Zooplankton\", color=cmap[2])\n", "plotlines(plot, 'x', 'D', source, \"Detritus\", color=cmap[3])\n", "\n", "\n", "# Plot aesthetics\n", "# Title\n", "plot.title.align = \"center\"\n", "plot.title.text_font_style = \"normal\"\n", "plot.title.text_font_size = \"14pt\"\n", "\n", "# Axes\n", "plot.yaxis.axis_label = 'Concentration (umolN per L)'\n", "plot.yaxis.axis_label_text_font_style = \"normal\"\n", "plot.yaxis.axis_label_text_font_size = \"10pt\"\n", "plot.xaxis.axis_label = 'Model Days'\n", "plot.xaxis.axis_label_text_font_style = \"normal\"\n", "plot.xaxis.axis_label_text_font_size = \"10pt\"\n", "\n", "# Grid\n", "plot.ygrid.grid_line_alpha = 0.2\n", "plot.xgrid.grid_line_alpha = 0\n", "plot.xgrid.minor_grid_line_color = 'grey'\n", "plot.xgrid.minor_grid_line_alpha = 0.2" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# STACKED BAR PLOT\n", "plot2 = figure(plot_width=900, plot_height=300,\n", " toolbar_location=\"right\", tools = \"save\",\n", " x_range=(1, NUM_STEPS + 1), y_range=plot.y_range, \n", " title=\"Nutrient Distribution\", webgl=True)\n", "\n", "# Plot data\n", "plot2.vbar('x', width=0.2, bottom=0, top='N', source=source, color=cmap[0])\n", "plot2.vbar('x', width=0.2, bottom='N', top='Psum', source=source, color=cmap[1])\n", "plot2.vbar('x', width=0.2, bottom='Psum', top='Zsum', source=source, color=cmap[2])\n", "plot2.vbar('x', width=0.2, bottom='Zsum', top='Dsum', source=source, color=cmap[3])\n", "\n", "# Aesthetics\n", "plot2.y_range.start = 0\n", "\n", "# Title\n", "plot2.title.align = \"center\"\n", "plot2.title.text_font_style = \"normal\"\n", "plot2.title.text_font_size = \"14pt\"\n", "\n", "# Axes\n", "plot2.yaxis.axis_label = 'Concentration (umolN per L)'\n", "plot2.yaxis.axis_label_text_font_style = \"normal\"\n", "plot2.yaxis.axis_label_text_font_size = \"10pt\"\n", "plot2.xaxis.axis_label = 'Model Days'\n", "plot2.xaxis.axis_label_text_font_style = \"normal\"\n", "plot2.xaxis.axis_label_text_font_size = \"10pt\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Javascript Interaction" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false, "scrolled": false }, "outputs": [], "source": [ "callback = CustomJS(args=dict(source=source), code=\"\"\"\n", " // Ingest main model data for modification\n", " var data = source.get('data');\n", " x = data['x'];\n", " N = data['N'];\n", " P = data['P'];\n", " Z = data['Z'];\n", " D = data['D'];\n", " // Again, these are optional and are to serve a stacked bar plot visualization.\n", " Psum = data['Psum'];\n", " Zsum = data['Zsum'];\n", " Dsum = data['Dsum'];\n", " \n", " // Parameters\n", " var Kn = 1; // Anything that is fixed and unable to be modified just gets the same value as the default view.\n", " var g = z_death.get('value'); // Anything that will be a slider or button gets this JS call.\n", " var lambda_Z = 0.2;\n", " var epsilon = p_death.get('value');\n", " var f = light.get('value');\n", " var dt = 1;\n", " var T = temperature.get('value');\n", " \n", " // Detritus-Related Parameters\n", " var alpha = 0.3;\n", " var beta = 0.6;\n", " var r = 0.15;\n", " var phi = 0.4;\n", " \n", " // Calculate Maximum Grazing Rate\n", " var a = 0.6;\n", " var b = 1.066;\n", " var Vm = a * Math.pow(b, T);\n", " \n", " // Zooplankton Species (impacts grazing rate)\n", " // Need to use IF statements for the button widget.\n", " var entry = zooSpecies.get('active');\n", " if (entry === 0) {\n", " var Rm = 1.6;\n", " } else if (entry === 1) {\n", " var Rm = 1.8;\n", " } else if (entry === 2) {\n", " var Rm = 1;\n", " } else {\n", " var Rm = 2;\n", " }\n", "\n", "\n", " // Initial Conditions with modifications allowed\n", " var N_0 = nut.get('value');\n", " var P_0 = phyto.get('value');\n", " var Z_0 = zoo.get('value');\n", " var D_0 = det.get('value');\n", " \n", " // Insert Initial Values for model\n", " N[0] = N_0;\n", " P[0] = P_0;\n", " Z[0] = Z_0;\n", " D[0] = D_0;\n", " Psum[0] = N_0 + P_0;\n", " Zsum[0] = N_0 + P_0 + Z_0;\n", " Dsum[0] = N_0 + P_0 + Z_0 + D_0;\n", " \n", " // Run Model\n", " for (i = 1; i < x.length; i++) {\n", " t = i - 1;\n", "\n", " // Common terms\n", " gamma_N = N[t] / (Kn + N[t])\n", " zoo_graze = Rm * (1 - Math.exp(-lambda_Z * P[t])) * Z[t]\n", " \n", " // Equation calculations for model\n", " N[i] = dt * (-Vm*gamma_N*f*P[t] + alpha*zoo_graze + epsilon*P[t] + g*Z[t] + phi*D[t]) + N[t];\n", " P[i] = dt * (Vm*gamma_N*f*P[t] - zoo_graze - epsilon*P[t] - r*P[t]) + P[t];\n", " Z[i] = dt * (beta*zoo_graze - g*Z[t]) + Z[t];\n", " D[i] = dt * (r*P[t] + (1-alpha-beta)*zoo_graze - phi*D[t]) + D[t];\n", " \n", " // Sum Variables\n", " Psum[i] = N[i] + P[i];\n", " Zsum[i] = N[i] + P[i] + Z[i];\n", " Dsum[i] = N[i] + P[i] + Z[i] + D[i];\n", " }\n", " source.trigger('change');\n", "\"\"\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Building Sliders and Buttons" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Nutrient Initial Conditions\n", "nut_slider = Slider(start = 0, end = 10, value = 4, step = 0.5, title = \"Initial Nutrient Concentration\", callback=callback)\n", "callback.args[\"nut\"] = nut_slider # The quoted \"nut\" is what is called in the JS interaction.\n", "\n", "# Phytoplankton Initial Conditions\n", "phyto_slider = Slider(start = 0, end = 10, value = 2.5, step = 0.5, title = \"Initial Phytoplankton Concentration\", callback=callback)\n", "callback.args[\"phyto\"] = phyto_slider\n", "\n", "# Zooplankton Initial Conditions\n", "zoo_slider = Slider(start = 0, end = 10, value = 1.5, step = 0.5, title = \"Initial Zooplankton Concentration\", callback=callback)\n", "callback.args[\"zoo\"] = zoo_slider\n", "\n", "# Detritus Initial Conditions\n", "det_slider = Slider(start = 0, end = 10, value = 0, step = 0.5, title = \"Initial Detritus Concentration\", callback=callback)\n", "callback.args[\"det\"] = det_slider\n", "\n", "# Ambient Temperature\n", "temp_slider = Slider(start = 0, end = 25, value = 15, step = 1, title = \"Water Temperature (degC)\", callback=callback)\n", "callback.args[\"temperature\"] = temp_slider\n", "\n", "# Phytoplankton Death Rate\n", "pdeath_slider = Slider(start = 0, end = 0.5, value = 0.1, step = 0.05, title = \"Phytoplankton Natural Death Rate (per day)\", callback=callback)\n", "callback.args[\"p_death\"] = pdeath_slider\n", "\n", "# Zooplankton Death Rate\n", "zdeath_slider = Slider(start = 0, end = 0.5, value = 0.2, step = 0.05, title = \"Zooplankton Natural Death Rate (per day)\", callback=callback)\n", "callback.args[\"z_death\"] = zdeath_slider\n", "\n", "# Light Intensity\n", "light_slider = Slider(start = 0, end = 1, value = 0.25, step = 0.05, title = \"Proportional Light Intensity\", callback=callback)\n", "callback.args[\"light\"] = light_slider\n", "\n", "# Zooplankton Species (for grazing)\n", "zoo_species = RadioButtonGroup(labels=[\"Cladoceran\", \"Copepod\", \"Mysid\", \"Rotifer\"], active=2, \n", " callback=callback)\n", "callback.args[\"zooSpecies\"] = zoo_species" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Final Layout and Saving the Plot" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "layout = gridplot([[nut_slider, phyto_slider, zoo_slider], [det_slider, pdeath_slider, zdeath_slider], \n", " [light_slider, temp_slider, zoo_species], \n", " [plot], [plot2]])\n", "\n", "# Option 1: Display in the Notebook\n", "show(layout)\n", "\n", "# Option 2: Save to HTML to run in browser\n", "# output_file('NPZD-Model.html')\n", "# save(layout)" ] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Python [default]", "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.5.1" } }, "nbformat": 4, "nbformat_minor": 1 }