\\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",
"