From dc69af35ac0a1bbcf7e1f79f9801427d69341561 Mon Sep 17 00:00:00 2001 From: Bakker Date: Thu, 18 Dec 2025 14:20:30 +0100 Subject: [PATCH 01/19] Made notebooks for example field studies --- examples/example_keesler.ipynb | 541 ++++++++++++++++ examples/example_kohler.ipynb | 374 +++++++++++ ...xample_mibitrans_anatrans_comparison.ipynb | 600 ++++++++++++++---- mibitrans/transport/models.py | 13 +- 4 files changed, 1405 insertions(+), 123 deletions(-) create mode 100644 examples/example_keesler.ipynb create mode 100644 examples/example_kohler.ipynb diff --git a/examples/example_keesler.ipynb b/examples/example_keesler.ipynb new file mode 100644 index 0000000..3ac21dc --- /dev/null +++ b/examples/example_keesler.ipynb @@ -0,0 +1,541 @@ +{ + "cells": [ + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-18T13:08:14.462867Z", + "start_time": "2025-12-18T13:08:14.456126Z" + } + }, + "cell_type": "code", + "source": [ + "import matplotlib.pyplot as plt\n", + "import mibitrans as mbt\n", + "import numpy as np" + ], + "id": "63370b00e73a3f77", + "outputs": [], + "execution_count": 31 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "### Field site example: Keesler Air-Force Base\n", + "\n", + "The distribution of BIOSCREEN v1.4 is accompanied by an field site example of the Keesler Air Force Base in Mississippi, USA. As the data is sourced from the USA, units are mixed imperial and metric. For the modelling below, all parameters are converted to metric for consistency [1].\n", + "\n", + "#### Site description\n", + "In 1987, during their removal, multiple underground storage tanks (UST) leaked a mixture of BTEX and lead to the groundwater. Remediation efforts included a 'bioventing' system and an in well aeration system. Monitoring wells were used to evaluate natural attenuation [2,3]. Local geology is a fine- to medium-grained sand, underlain by a clay layer at 20ft depth. Bottom of sandy layer has local presence of peat. Thickness and continuity of clay layer are unknown, but not every boring showed the clay layer at 20ft. Assumption of clay continuous clay layer was made. Unconfined aquifer with groundwater levels varying between 5 and 9 ft below ground level. Groundwater flow is to the north-east, which eventually discharges into the Back Bay of Biloxi 2100ft downgradient [4]. Values of hydraulic gradient and conductivity vary, with values of 0.003 to 0.0083 ft/ft and 40, 61, 32 ft/day listed in [4]. In [1]. the values of 0.003 ft/ft and 0.011 cm/sec (31.2 ft/day) were used. Effective porosity is estimated to be 0.25 [4], resulting in groundwater velocity of 0.8 ft/day. Although [1] uses an effective porosity of 0.3, and results in groundwater velocity of 0.31 ft/day. For consistency, values reported in [1] are used in the modelling. Dispersivity values were based on estimated plume length of 280ft [1]. With 13.3, 1.3 and 0 for longitudinal, transverse horizontal and transverse vertical respectively. After calibration, these values were changed to 32.5, 3.25 and 0 respectively [1]." + ], + "id": "ab077fc2fd5e825" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-18T13:08:14.603830Z", + "start_time": "2025-12-18T13:08:14.593264Z" + } + }, + "cell_type": "code", + "source": [ + "ft = 3.281 #Conversion factor ft/m\n", + "\n", + "hydro_pars = mbt.HydrologicalParameters(\n", + " h_conductivity = 0.011 / 100 * 3600 * 24, #m/d\n", + " h_gradient = 0.003, # m/m\n", + " porosity = 0.3,\n", + " alpha_x = 32.5 / ft, #m\n", + " alpha_y = 3.25 / ft, #m\n", + " alpha_z = 0\n", + ")\n", + "print(hydro_pars.velocity)" + ], + "id": "d03bc12d32df84ac", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.09504\n" + ] + } + ], + "execution_count": 32 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "The fraction of organic carbon is 0.000057 based of lab analysis, soil bulk density is estimated to be 1.7 (kg/L) [1]. Partition coefficient of BTEX differs by order of magnitude (38, 135, 95, 240 L/kg for B, T, E, X), [1] uses the value of benzene; 38 L/kg. Electron acceptor concentrations are based on groundwater sampling performed in 1995. For oxygen, nitrate and sulfate, the difference between background concentration in the aquifer and minimum concentrations in the plume are used. For ferrous iron and methane, average concentrations in the plume area are used. Values differ somewhat from those report in [4] (elaborate?).", + "id": "bb52ab633659cab4" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-18T13:08:14.694489Z", + "start_time": "2025-12-18T13:08:14.684650Z" + } + }, + "cell_type": "code", + "source": [ + "att_pars = mbt.AttenuationParameters(\n", + " bulk_density=1.7, # kg/L (note; for consistency, should use g/m3, but since units cancel out here for calculation, using more intuitive kg/L instead)\n", + " partition_coefficient=38, # L/kg (units cancel out with bulk density)\n", + " fraction_organic_carbon=0.000057,\n", + " decay_rate = 0, # 1/day, for now, we do not consider linear decay rate.\n", + ")\n", + "att_pars.calculate_retardation(hydro_pars.porosity)\n", + "print(\"Retardation based on input values =\", att_pars.retardation)\n", + "\n", + "electron_acceptor_concentrations = mbt.ElectronAcceptors(\n", + " delta_oxygen=2.05 - 0.4, #background conc - minimum conc, [g/m3]\n", + " delta_nitrate=0.07 - 0, #background conc - minimum conc, [g/m3]\n", + " ferrous_iron=16.6, #average conc, [g/m3]\n", + " delta_sulfate=26.2 - 3.8, #background conc - minimum conc, [g/m3]\n", + " methane=6.6, #average conc, [g/m3]\n", + ")" + ], + "id": "6688d6a2be9895a8", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Retardation based on input values = 1.012274\n" + ] + } + ], + "execution_count": 33 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Conditions and parameters of the contaminant source were inferred from BTEX monitoring data and geologic logs [1]. The source thickness is estimated to be 10ft, with a total 2000kg of pure BTEX. Total source width of 130ft, with 28ft zone of 0.057 g/m3, 30ft zone of 2.508 g/m3, 14ft zone of 13.68, 30ft zone of 2.508 g/m3, 28ft zone of 0.057 g/m3.", + "id": "a5c180d376ce7795" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-18T13:08:14.835226Z", + "start_time": "2025-12-18T13:08:14.827151Z" + } + }, + "cell_type": "code", + "source": [ + "source_pars = mbt.SourceParameters(\n", + " # Note; as plume is symmetric, source is described from center outwards in y-direction.\n", + " source_zone_boundary=np.array([7/ft, 37/ft, 65/ft]), #[m]\n", + " source_zone_concentration=np.array([13.68, 2.508, 0.057]), #[g/m3]\n", + " depth=10/ft, #[m]\n", + " total_mass=2000000, #[g]\n", + ")" + ], + "id": "72fbc1542ca07cd0", + "outputs": [], + "execution_count": 34 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Model dimensions used for the BIOSCREEN model are; length = 320ft, width = 200ft, duration=6y [1]. Model resolution is always a fraction of model dimensions, 1/10 for length, 1/4 for width and 1/10 for time.", + "id": "bcf58154d070a76f" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-18T13:08:14.919142Z", + "start_time": "2025-12-18T13:08:14.910225Z" + } + }, + "cell_type": "code", + "source": [ + "model_pars = mbt.ModelParameters(model_length=1000/ft,\n", + " model_width=200/ft,\n", + " model_time=10*365,\n", + " dx=5/ft,\n", + " dy=1/ft,\n", + " dt=365/5)" + ], + "id": "b0215d655ce763b5", + "outputs": [], + "execution_count": 35 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-18T13:08:15.143295Z", + "start_time": "2025-12-18T13:08:15.058451Z" + } + }, + "cell_type": "code", + "source": [ + "bio_model = mbt.Bioscreen(hydrological_parameters=hydro_pars,\n", + " attenuation_parameters=att_pars,\n", + " source_parameters=source_pars,\n", + " model_parameters=model_pars,)\n", + "bio_results = bio_model.run()\n", + "bio_results.centerline()\n", + "plt.show()\n", + "print(bio_results.attenuation_parameters.retardation)\n", + "print(bio_results.rv)\n", + "print(bio_results.hydrological_parameters.velocity)\n" + ], + "id": "1fd5d083a23264c5", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1.012274\n", + "0.09388762331147496\n", + "0.09504\n" + ] + } + ], + "execution_count": 36 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-18T13:08:15.656797Z", + "start_time": "2025-12-18T13:08:15.575943Z" + } + }, + "cell_type": "code", + "source": [ + "ana_model = mbt.Anatrans(hydrological_parameters=hydro_pars,\n", + " attenuation_parameters=att_pars,\n", + " source_parameters=source_pars,\n", + " model_parameters=model_pars,)\n", + "ana_results = ana_model.run()\n", + "ana_results.centerline()\n", + "plt.show()" + ], + "id": "bfe447c47e49d8e6", + "outputs": [], + "execution_count": 37 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-18T13:08:21.651217Z", + "start_time": "2025-12-18T13:08:16.016435Z" + } + }, + "cell_type": "code", + "source": [ + "mbt_model = mbt.Mibitrans(hydrological_parameters=hydro_pars,\n", + " attenuation_parameters=att_pars,\n", + " source_parameters=source_pars,\n", + " model_parameters=model_pars)\n", + "mbt_results =mbt_model.run()\n", + "mbt_results.centerline()\n", + "plt.show()" + ], + "id": "451b06b489120021", + "outputs": [], + "execution_count": 38 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-18T13:08:22.053224Z", + "start_time": "2025-12-18T13:08:22.028450Z" + } + }, + "cell_type": "code", + "source": [ + "time_point = 365/5\n", + "mbt_results.centerline(time=time_point, label=\"mbt\", color=\"blue\")\n", + "ana_results.centerline(time=time_point, label=\"ana\", color=\"red\", linestyle=\"--\")\n", + "bio_results.centerline(time=time_point, label=\"bio\", color=\"green\", linestyle=\":\")\n", + "plt.show()" + ], + "id": "4d1b4436f0aa185b", + "outputs": [], + "execution_count": 39 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-18T13:08:22.280579Z", + "start_time": "2025-12-18T13:08:22.277190Z" + } + }, + "cell_type": "code", + "source": "%matplotlib notebook", + "id": "ad0e7d335620f2c2", + "outputs": [], + "execution_count": 40 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-18T13:08:22.385383Z", + "start_time": "2025-12-18T13:08:22.309886Z" + } + }, + "cell_type": "code", + "source": [ + "anim = mbt.centerline([mbt_results, ana_results, bio_results], legend_names=[\"mbt\", \"ana\", \"bio\"], animate=True)\n", + "plt.show()" + ], + "id": "b3887af76478a217", + "outputs": [ + { + "data": { + "text/plain": [ + "" + ], + "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "C:\\Users\\4662504\\Documents\\Projects\\mibitrans\\.venv\\Lib\\site-packages\\matplotlib\\animation.py:908: UserWarning: Animation was deleted without rendering anything. This is most likely not intended. To prevent deletion, assign the Animation to a variable, e.g. `anim`, that exists until you output the Animation using `plt.show()` or `anim.save()`.\n", + " warnings.warn(\n" + ] + } + ], + "execution_count": 41 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-18T13:08:26.940499Z", + "start_time": "2025-12-18T13:08:22.459199Z" + } + }, + "cell_type": "code", + "source": [ + "bio_model.instant_reaction(electron_acceptors=electron_acceptor_concentrations)\n", + "ana_model.instant_reaction(electron_acceptors=electron_acceptor_concentrations)\n", + "mbt_model.instant_reaction(electron_acceptors=electron_acceptor_concentrations)\n", + "bio_results_instant = bio_model.run()\n", + "ana_results_instant = ana_model.run()\n", + "mbt_results_instant = mbt_model.run()" + ], + "id": "d7f2a3f0f078a615", + "outputs": [], + "execution_count": 42 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-18T13:08:27.206056Z", + "start_time": "2025-12-18T13:08:27.178002Z" + } + }, + "cell_type": "code", + "source": [ + "anim = mbt.centerline([mbt_results_instant, ana_results_instant, bio_results_instant], legend_names=[\"mbt\", \"ana\", \"bio\"], animate=True)\n", + "plt.show()" + ], + "id": "e2c45a51f2c472d3", + "outputs": [ + { + "data": { + "text/plain": [ + "" + ], + "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + } + ], + "execution_count": 43 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-18T13:08:27.387237Z", + "start_time": "2025-12-18T13:08:27.360869Z" + } + }, + "cell_type": "code", + "source": [ + "time_point = 365 * 5\n", + "plt.figure()\n", + "mbt_results_instant.centerline(time=time_point, label=\"mbt\", color=\"blue\")\n", + "ana_results_instant.centerline(time=time_point, label=\"ana\", color=\"red\", linestyle=\"--\")\n", + "bio_results_instant.centerline(time=time_point, label=\"bio\", color=\"green\", linestyle=\":\")\n", + "\n", + "\n", + "field_data_x = np.array([0, 32/ft, 64/ft, 192/ft, 288/ft])\n", + "field_data_c = np.array([12,5,1,0.5,0.001])\n", + "plt.scatter(field_data_x, field_data_c, color=\"black\")\n", + "plt.xlim(0,150)\n", + "plt.show()" + ], + "id": "6ec97664c7c1f918", + "outputs": [ + { + "data": { + "text/plain": [ + "" + ], + "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + } + ], + "execution_count": 44 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "According to Bioscreen, the above model predicts the measured field concentrations fairly well", + "id": "eb41bc730334f094" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-18T13:08:27.428314Z", + "start_time": "2025-12-18T13:08:27.403954Z" + } + }, + "cell_type": "code", + "outputs": [ + { + "data": { + "text/plain": [ + "" + ], + "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + } + ], + "execution_count": 45, + "source": [ + "plt.figure()\n", + "plt.plot(mbt_results_instant.x, mbt_results_instant.cxyt_noBC[-1,100,:])\n", + "plt.plot(ana_results_instant.x, ana_results_instant.cxyt_noBC[-1, 100, :])\n", + "plt.plot(bio_results_instant.x, bio_results_instant.cxyt_noBC[-1, 100, :])\n", + "plt.show()" + ], + "id": "5ec0210f2f0340ab" + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "(1) Newell, C. J., McLeod, R. K., & Gonzales, J. R. (1997). BIOSCREEN natural attenuation decision support\n", + " system version 1.4 revisions, Tech. rep., U.S. EPA.\n", + "\n", + "(2)https://webapp1.dlib.indiana.edu/virtual_disk_library/index.cgi/4908754/FID2958/abstracts/00000166.html (make actual citation, maybe not include, since is summary of (3)\n", + "\n", + "(3) COST AND PERFORMANCE CASE STUDY REPORT - Keesler Air Force Base Base Exchange Service Station (make actual citation)\n", + "\n", + "(4) Corrective action plan for the risk-based closure of the base exchange service station, area of concern - A (ST-06) Keesler Air Froce Base, Mississippi (make actual citation)\n" + ], + "id": "eb969db6ab0c1424" + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "https://www.sunherald.com/news/local/military/article234197522.html", + "id": "a95d32a3185dea0" + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/example_kohler.ipynb b/examples/example_kohler.ipynb new file mode 100644 index 0000000..90584e7 --- /dev/null +++ b/examples/example_kohler.ipynb @@ -0,0 +1,374 @@ +{ + "cells": [ + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-16T14:10:01.500184Z", + "start_time": "2025-12-16T14:10:01.495897Z" + } + }, + "cell_type": "code", + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "\n", + "from mibitrans import ElectronAcceptors\n", + "from mibitrans import UtilizationFactor\n", + "from mibitrans.data.parameters import AttenuationParameters\n", + "from mibitrans.data.parameters import HydrologicalParameters\n", + "from mibitrans.data.parameters import ModelParameters\n", + "from mibitrans.data.parameters import SourceParameters\n", + "from mibitrans.transport.models import Anatrans\n", + "from mibitrans.transport.models import Mibitrans\n", + "from mibitrans.visualize.plot_line import centerline" + ], + "id": "ac421af9f5d936aa", + "outputs": [], + "execution_count": 22 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Note: For comparison to steady state, value of flow velocity does not matter, since it only determines how fast steady state will be accomplished.", + "id": "524f38b7b357f749" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-16T14:10:59.438453Z", + "start_time": "2025-12-16T14:10:59.433389Z" + } + }, + "cell_type": "code", + "source": [ + "hydro = HydrologicalParameters(\n", + " velocity=0.1,\n", + " porosity=0.25,\n", + " alpha_x=2,\n", + " alpha_y=0.05,\n", + " alpha_z=0\n", + ")\n", + "att = AttenuationParameters(\n", + " decay_rate=0,\n", + " retardation=1\n", + ")\n", + "source = SourceParameters(\n", + " source_zone_boundary=np.array([1, 1.5, 2]),\n", + " source_zone_concentration=np.array([10, 5, 1]),\n", + " total_mass=\"inf\",\n", + " depth=1,\n", + ")\n", + "model = ModelParameters(\n", + " model_length=400,\n", + " model_width=10,\n", + " model_time=20*365,\n", + " dx=2,\n", + " dy=0.1,\n", + " dt=365 / 5,\n", + ")" + ], + "id": "8734f631ad0baf94", + "outputs": [], + "execution_count": 24 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-16T14:11:01.496158Z", + "start_time": "2025-12-16T14:11:01.321526Z" + } + }, + "cell_type": "code", + "source": [ + "ana_obj = Anatrans(hydro, att, source, model)\n", + "ana_obj.instant_reaction(ElectronAcceptors(8, 0, 0, 0, 0), UtilizationFactor(3.5,1,1,1,1))\n", + "results_ana = ana_obj.run()" + ], + "id": "23b7c98fb94ec32a", + "outputs": [], + "execution_count": 25 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-16T14:11:03.330927Z", + "start_time": "2025-12-16T14:11:03.323421Z" + } + }, + "cell_type": "code", + "source": [ + "# Needed to show animations in Jupyter Notebooks\n", + "%matplotlib notebook" + ], + "id": "ecaadf8f8cbc3343", + "outputs": [], + "execution_count": 26 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-16T14:11:04.863065Z", + "start_time": "2025-12-16T14:11:04.832695Z" + } + }, + "cell_type": "code", + "source": [ + "anim = results_ana.centerline(animate=True)\n", + "plt.show()" + ], + "id": "a17c9965f5dadfc0", + "outputs": [ + { + "data": { + "text/plain": [ + "" + ], + "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + } + ], + "execution_count": 27 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-16T14:11:22.700774Z", + "start_time": "2025-12-16T14:11:22.555055Z" + } + }, + "cell_type": "code", + "source": [ + "anim = results_ana.plume_3d(animate=True, cmap=\"viridis\")\n", + "plt.show()" + ], + "id": "40d59891433008e2", + "outputs": [ + { + "data": { + "text/plain": [ + "" + ], + "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + } + ], + "execution_count": 28 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-16T14:11:48.713513Z", + "start_time": "2025-12-16T14:11:36.237823Z" + } + }, + "cell_type": "code", + "source": [ + "mbt_obj = Mibitrans(hydro, att, source, model)\n", + "mbt_obj.instant_reaction(ElectronAcceptors(8, 0, 0, 0, 0), UtilizationFactor(3.5,1,1,1,1))\n", + "results_mbt = mbt_obj.run()" + ], + "id": "35cc9be16b364665", + "outputs": [], + "execution_count": 29 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-16T14:11:50.444698Z", + "start_time": "2025-12-16T14:11:50.420644Z" + } + }, + "cell_type": "code", + "source": [ + "anim = results_mbt.centerline(animate=True)\n", + "plt.show()" + ], + "id": "10ce7f57ea8be225", + "outputs": [ + { + "data": { + "text/plain": [ + "" + ], + "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + } + ], + "execution_count": 30 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-16T14:12:08.750578Z", + "start_time": "2025-12-16T14:12:08.712444Z" + } + }, + "cell_type": "code", + "source": [ + "plt.figure()\n", + "results_ana.centerline(color=\"orange\", label=\"anatrans\")\n", + "results_mbt.centerline(color=\"green\", label=\"mibitrans\", linestyle=\"--\")\n", + "plt.legend()\n", + "plt.show()" + ], + "id": "2b161f395cf35c99", + "outputs": [ + { + "data": { + "text/plain": [ + "" + ], + "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + } + ], + "execution_count": 31 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-16T14:12:16.082421Z", + "start_time": "2025-12-16T14:12:16.055593Z" + } + }, + "cell_type": "code", + "source": [ + "anim = centerline([results_ana, results_mbt], legend_names=[\"anatrans\", \"mibitrans\"], animate=True)\n", + "plt.show()" + ], + "id": "c4e911cc72a4ee39", + "outputs": [ + { + "data": { + "text/plain": [ + "" + ], + "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + } + ], + "execution_count": 32 + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/example_mibitrans_anatrans_comparison.ipynb b/examples/example_mibitrans_anatrans_comparison.ipynb index c4110de..ec224d1 100644 --- a/examples/example_mibitrans_anatrans_comparison.ipynb +++ b/examples/example_mibitrans_anatrans_comparison.ipynb @@ -2,10 +2,13 @@ "cells": [ { "cell_type": "code", - "execution_count": null, "id": "initial_id", - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-16T13:43:27.435567Z", + "start_time": "2025-12-16T13:43:25.761575Z" + } + }, "source": [ "import matplotlib.pyplot as plt\n", "import numpy as np\n", @@ -16,7 +19,9 @@ "from mibitrans.transport.models import Anatrans\n", "from mibitrans.transport.models import Mibitrans\n", "from mibitrans.visualize.animation import animate_1d" - ] + ], + "outputs": [], + "execution_count": 1 }, { "cell_type": "markdown", @@ -35,10 +40,13 @@ }, { "cell_type": "code", - "execution_count": null, "id": "29cec10a2865e2ca", - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-16T13:43:30.359745Z", + "start_time": "2025-12-16T13:43:30.353713Z" + } + }, "source": [ "hydro = HydrologicalParameters(\n", " velocity=0.277, # Flow velocity [m/d]\n", @@ -77,45 +85,60 @@ " # Model time discretization step size, in [days]\n", " dt = 25\n", ")" - ] + ], + "outputs": [], + "execution_count": 2 }, { "cell_type": "code", - "execution_count": null, "id": "54e9e7e491c986fe", - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-16T13:43:35.973143Z", + "start_time": "2025-12-16T13:43:32.757383Z" + } + }, "source": [ "mbt_object = Mibitrans(hydro, att, source, model)\n", - "mbt_object.run()\n", + "mbt_results = mbt_object.run()\n", "\n", "ana_object = Anatrans(hydro, att, source, model)\n", - "ana_object.run()" - ] + "ana_results = ana_object.run()" + ], + "outputs": [], + "execution_count": 3 }, { "cell_type": "code", - "execution_count": null, "id": "e323802bcb322adf", - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-16T13:43:38.143992Z", + "start_time": "2025-12-16T13:43:38.124893Z" + } + }, "source": [ "%matplotlib notebook" - ] + ], + "outputs": [], + "execution_count": 4 }, { "cell_type": "code", - "execution_count": null, "id": "b3d53214555ede3a", - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-16T13:43:39.946675Z", + "start_time": "2025-12-16T13:43:39.611526Z" + } + }, "source": [ - "ani = animate_1d(x_axis_parameter=mbt_object.y,\n", - " y_axis_parameter=[mbt_object.cxyt[:,:,25], mbt_object.cxyt[:,:,50],\n", - " mbt_object.cxyt[:,:,100],mbt_object.cxyt[:,:,200],\n", - " ana_object.cxyt[:,:,25], ana_object.cxyt[:,:,50],\n", - " ana_object.cxyt[:,:,100], ana_object.cxyt[:,:,200]],\n", - " time_parameter=mbt_object.t,\n", + "ani = animate_1d(x_axis_parameter=mbt_results.y,\n", + " y_axis_parameter=[mbt_results.cxyt[:,:,25], mbt_results.cxyt[:,:,50],\n", + " mbt_results.cxyt[:,:,100],mbt_results.cxyt[:,:,200],\n", + " ana_results.cxyt[:,:,25], ana_results.cxyt[:,:,50],\n", + " ana_results.cxyt[:,:,100], ana_results.cxyt[:,:,200]],\n", + " time_parameter=mbt_results.t,\n", " y_colors=[\"darkgreen\", \"limegreen\", \"cornflowerblue\", \"blue\",\n", " \"green\", \"greenyellow\", \"darkturquoise\", \"dodgerblue\"],\n", " y_names=[\"Mibitrans x=50m\", \"Mibitrans x=100m\", \"Mibitrans x=200m\", \"Mibitrans x=400m\",\n", @@ -123,21 +146,55 @@ " linestyle=[\"-\", \"-\", \"-\", \"-\", \"--\", \"--\", \"--\", \"--\"])\n", "plt.xlabel(\"y-position [m]\")\n", "plt.show()\n" - ] + ], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ], + "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + } + ], + "execution_count": 5 }, { "cell_type": "code", - "execution_count": null, "id": "78a4f70e30d859ea", - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-16T13:44:04.330475Z", + "start_time": "2025-12-16T13:44:04.299167Z" + } + }, "source": [ - "ani1 = animate_1d(x_axis_parameter=mbt_object.x,\n", - " y_axis_parameter=[mbt_object.cxyt[:,75,:], mbt_object.cxyt[:,84,:],\n", - " mbt_object.cxyt[:,100,:], mbt_object.cxyt[:,125,:],\n", - " ana_object.cxyt[:,75,:], ana_object.cxyt[:,84,:],\n", - " ana_object.cxyt[:,100,:], ana_object.cxyt[:,125,:]],\n", - " time_parameter=mbt_object.t,\n", + "ani1 = animate_1d(x_axis_parameter=mbt_results.x,\n", + " y_axis_parameter=[mbt_results.cxyt[:,75,:], mbt_results.cxyt[:,84,:],\n", + " mbt_results.cxyt[:,100,:], mbt_results.cxyt[:,125,:],\n", + " ana_results.cxyt[:,75,:], ana_results.cxyt[:,84,:],\n", + " ana_results.cxyt[:,100,:], ana_results.cxyt[:,125,:]],\n", + " time_parameter=mbt_results.t,\n", " y_colors=[\"darkgreen\", \"limegreen\", \"cornflowerblue\", \"blue\",\n", " \"green\", \"greenyellow\", \"darkturquoise\", \"dodgerblue\"],\n", " y_names=[\"Mibitrans y=0m\", \"Mibitrans y=9m\", \"Mibitrans y=25m\", \"Mibitrans y=50m\",\n", @@ -145,28 +202,93 @@ " linestyle=[\"-\", \"-\", \"-\", \"-\", \"--\", \"--\", \"--\", \"--\"])\n", "plt.xlabel(\"x-position [m]\")\n", "plt.show()\n" - ] + ], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ], + "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + } + ], + "execution_count": 6 }, { "cell_type": "code", - "execution_count": null, "id": "77b3c43e8ed23283", - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-16T13:44:39.082231Z", + "start_time": "2025-12-16T13:44:39.039946Z" + } + }, "source": [ "plt.figure()\n", - "mbt_object.breakthrough(50, 0, color=\"darkgreen\", linestyle=\"-\", label=\"Mibitrans 50m\")\n", - "mbt_object.breakthrough(100, 0, color=\"limegreen\", linestyle=\"-\", label=\"Mibitrans 100m\")\n", - "mbt_object.breakthrough(200, 0, color=\"cornflowerblue\", linestyle=\"-\", label=\"Mibitrans 200m\")\n", - "mbt_object.breakthrough(400, 0, color=\"blue\", linestyle=\"-\", label=\"Mibitrans 400m\")\n", - "ana_object.breakthrough(50, 0, color=\"green\", linestyle=\"--\", label=\"Anatrans 50m\")\n", - "ana_object.breakthrough(100, 0, color=\"greenyellow\", linestyle=\"--\", label=\"Anatrans 100m\")\n", - "ana_object.breakthrough(200, 0, color=\"darkturquoise\", linestyle=\"--\", label=\"Anatrans 200m\")\n", - "ana_object.breakthrough(400, 0, color=\"dodgerblue\", linestyle=\"--\", label=\"Anatrans 400m\")\n", + "mbt_results.breakthrough(50, 0, color=\"darkgreen\", linestyle=\"-\", label=\"Mibitrans 50m\")\n", + "mbt_results.breakthrough(100, 0, color=\"limegreen\", linestyle=\"-\", label=\"Mibitrans 100m\")\n", + "mbt_results.breakthrough(200, 0, color=\"cornflowerblue\", linestyle=\"-\", label=\"Mibitrans 200m\")\n", + "mbt_results.breakthrough(400, 0, color=\"blue\", linestyle=\"-\", label=\"Mibitrans 400m\")\n", + "ana_results.breakthrough(50, 0, color=\"green\", linestyle=\"--\", label=\"Anatrans 50m\")\n", + "ana_results.breakthrough(100, 0, color=\"greenyellow\", linestyle=\"--\", label=\"Anatrans 100m\")\n", + "ana_results.breakthrough(200, 0, color=\"darkturquoise\", linestyle=\"--\", label=\"Anatrans 200m\")\n", + "ana_results.breakthrough(400, 0, color=\"dodgerblue\", linestyle=\"--\", label=\"Anatrans 400m\")\n", "plt.title(\"Breakthrough plot of Mibitrans and Anatrans model, at various x locations.\")\n", "plt.legend()\n", "plt.show()" - ] + ], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ], + "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + } + ], + "execution_count": 7 }, { "cell_type": "markdown", @@ -178,75 +300,163 @@ }, { "cell_type": "code", - "execution_count": null, "id": "ae416dc23dd6b334", - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-16T13:46:57.576523Z", + "start_time": "2025-12-16T13:46:37.590707Z" + } + }, "source": [ "parameter_list = [0.1, 0.5, 1, 5, 10]\n", "output_list_mbt = []\n", "output_list_ana = []\n", "for par in parameter_list:\n", " print(f\"starting run with par = {par}\")\n", - " mbt_object = Mibitrans(hydro, att, source, model)\n", - " mbt_object.hydrological_parameters.alpha_x = par\n", - " mbt_object.hydrological_parameters.alpha_y = par / 10\n", - " mbt_object.hydrological_parameters.alpha_z = par / 100\n", - " mbt_object.run()\n", - " output_list_mbt.append(mbt_object.cxyt)\n", + " mbt_object_alpha = Mibitrans(hydro, att, source, model)\n", + " mbt_object_alpha.hydrological_parameters.alpha_x = par\n", + " mbt_object_alpha.hydrological_parameters.alpha_y = par / 10\n", + " mbt_object_alpha.hydrological_parameters.alpha_z = par / 100\n", + " mbt_res_alp = mbt_object_alpha.run()\n", + " output_list_mbt.append(mbt_res_alp.cxyt)\n", "\n", - " ana_object = Anatrans(hydro, att, source, model)\n", - " ana_object.hydrological_parameters.alpha_x = par\n", - " ana_object.hydrological_parameters.alpha_y = par / 10\n", - " ana_object.hydrological_parameters.alpha_z = par / 100\n", - " ana_object.run()\n", - " output_list_ana.append(ana_object.cxyt)" - ] + " ana_object_alpha = Anatrans(hydro, att, source, model)\n", + " ana_object_alpha.hydrological_parameters.alpha_x = par\n", + " ana_object_alpha.hydrological_parameters.alpha_y = par / 10\n", + " ana_object_alpha.hydrological_parameters.alpha_z = par / 100\n", + " ana_res_alp = ana_object_alpha.run()\n", + " output_list_ana.append(ana_res_alp.cxyt)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "starting run with par = 0.1\n", + "starting run with par = 0.5\n", + "starting run with par = 1\n", + "starting run with par = 5\n", + "starting run with par = 10\n" + ] + } + ], + "execution_count": 10 }, { "cell_type": "code", - "execution_count": null, "id": "93a0274d7116f5f", - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-16T13:47:00.001060Z", + "start_time": "2025-12-16T13:46:59.952945Z" + } + }, "source": [ "colors_mbt = [\"darkgreen\", \"limegreen\", \"cornflowerblue\", \"blue\", \"indigo\"]\n", "colors_ana = [\"green\", \"greenyellow\", \"darkturquoise\", \"dodgerblue\", \"darkviolet\"]\n", "plt.figure()\n", "for i, conc in enumerate(output_list_mbt):\n", - " plt.plot(mbt_object.x, conc[-1,75,:], color=colors_mbt[i], label=f\"Mibitrans a={parameter_list[i]}m\")\n", + " plt.plot(mbt_res_alp.x, conc[-1,75,:], color=colors_mbt[i], label=f\"Mibitrans a={parameter_list[i]}m\")\n", "for i, conc in enumerate(output_list_ana):\n", - " plt.plot(ana_object.x, conc[-1,75,:], color=colors_ana[i], linestyle=\"--\", label=f\"Anatrans a={parameter_list[i]}m\")\n", + " plt.plot(ana_res_alp.x, conc[-1,75,:], color=colors_ana[i], linestyle=\"--\", label=f\"Anatrans a={parameter_list[i]}m\")\n", "plt.legend()\n", "plt.show()" - ] + ], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ], + "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + } + ], + "execution_count": 11 }, { "cell_type": "code", - "execution_count": null, "id": "ea1d7ea112addeff", - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-16T13:47:19.803283Z", + "start_time": "2025-12-16T13:47:19.759187Z" + } + }, "source": [ "colors_mbt = [\"darkgreen\", \"limegreen\", \"cornflowerblue\", \"blue\", \"indigo\"]\n", "colors_ana = [\"green\", \"greenyellow\", \"darkturquoise\", \"dodgerblue\", \"darkviolet\"]\n", "plt.figure()\n", "for i, conc in enumerate(output_list_mbt):\n", - " plt.plot(mbt_object.y, conc[-1,:,175], color=colors_mbt[i],\n", + " plt.plot(mbt_res_alp.y, conc[-1,:,175], color=colors_mbt[i],\n", " label=f\"Mibitrans a={parameter_list[i]}m\")\n", "for i, conc in enumerate(output_list_ana):\n", - " plt.plot(ana_object.y, conc[-1,:,175], color=colors_ana[i],\n", + " plt.plot(ana_res_alp.y, conc[-1,:,175], color=colors_ana[i],\n", " linestyle=\"--\", label=f\"Anatrans a={parameter_list[i]}m\")\n", "plt.legend()\n", "plt.show()" - ] + ], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ], + "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + } + ], + "execution_count": 12 }, { "cell_type": "code", - "execution_count": null, "id": "2e8640aff2864ca7", - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-16T13:48:19.412475Z", + "start_time": "2025-12-16T13:47:58.834855Z" + } + }, "source": [ "parameter_list_v = [0.1, 0.1, 0.1, 0.1, 0.1]\n", "parameter_list_a = [10, 5, 2, 1, 0.5]\n", @@ -256,78 +466,163 @@ " parv = parameter_list_v[i]\n", " para = parameter_list_a[i]\n", " print(f\"starting run with parv = {parv} and para = {para}\")\n", - " mbt_object = Mibitrans(hydro, att, source, model)\n", - " mbt_object.hydrological_parameters.velocity = parv\n", - " mbt_object.hydrological_parameters.alpha_x = para\n", - " mbt_object.hydrological_parameters.alpha_y = para / 10\n", - " mbt_object.hydrological_parameters.alpha_z = 0#para / 100\n", - " mbt_object.run()\n", - " output_list_mbt_v.append(mbt_object.cxyt)\n", + " mbt_va = Mibitrans(hydro, att, source, model)\n", + " mbt_va.hydrological_parameters.velocity = parv\n", + " mbt_va.hydrological_parameters.alpha_x = para\n", + " mbt_va.hydrological_parameters.alpha_y = para / 10\n", + " mbt_va.hydrological_parameters.alpha_z = 0#para / 100\n", + " mbt_res_va = mbt_va.run()\n", + " output_list_mbt_v.append(mbt_res_va.cxyt)\n", "\n", - " ana_object = Anatrans(hydro, att, source, model)\n", - " ana_object.hydrological_parameters.velocity = parv\n", - " ana_object.hydrological_parameters.alpha_x = para\n", - " ana_object.hydrological_parameters.alpha_y = para / 10\n", - " ana_object.hydrological_parameters.alpha_z = 0#para / 100\n", - " ana_object.run()\n", - " output_list_ana_v.append(ana_object.cxyt)" - ] + " ana_va = Anatrans(hydro, att, source, model)\n", + " ana_va.hydrological_parameters.velocity = parv\n", + " ana_va.hydrological_parameters.alpha_x = para\n", + " ana_va.hydrological_parameters.alpha_y = para / 10\n", + " ana_va.hydrological_parameters.alpha_z = 0#para / 100\n", + " ana_res_va = ana_va.run()\n", + " output_list_ana_v.append(ana_res_va.cxyt)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "starting run with parv = 0.1 and para = 10\n", + "starting run with parv = 0.1 and para = 5\n", + "starting run with parv = 0.1 and para = 2\n", + "starting run with parv = 0.1 and para = 1\n", + "starting run with parv = 0.1 and para = 0.5\n" + ] + } + ], + "execution_count": 13 }, { "cell_type": "code", - "execution_count": null, "id": "f5383ae968db9bf6", - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-16T13:48:42.428498Z", + "start_time": "2025-12-16T13:48:42.377594Z" + } + }, "source": [ "colors_mbt = [\"darkgreen\", \"limegreen\", \"cornflowerblue\", \"blue\", \"indigo\"]\n", "colors_ana = [\"green\", \"greenyellow\", \"darkturquoise\", \"dodgerblue\", \"darkviolet\"]\n", "plt.figure()\n", "for i, conc in enumerate(output_list_mbt_v):\n", - " plt.plot(mbt_object.x, conc[-1,75,:], color=colors_mbt[i],\n", + " plt.plot(mbt_res_va.x, conc[-1,75,:], color=colors_mbt[i],\n", " label=f\"Mbt v={parameter_list_v[i]}m/d, a={parameter_list_a[i]}m\")\n", "for i, conc in enumerate(output_list_ana_v):\n", - " plt.plot(ana_object.x, conc[-1,75,:], color=colors_ana[i],\n", + " plt.plot(ana_res_va.x, conc[-1,75,:], color=colors_ana[i],\n", " linestyle=\"--\", label=f\"Ana v={parameter_list_v[i]}m/d, a={parameter_list_a[i]}m\")\n", "plt.xlim((0,300))\n", "plt.legend()\n", "plt.show()" - ] + ], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ], + "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + } + ], + "execution_count": 14 }, { "cell_type": "code", - "execution_count": null, "id": "f38d34e6b66ce99e", - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-16T13:48:55.435247Z", + "start_time": "2025-12-16T13:48:55.404510Z" + } + }, "source": [ "colors_mbt = [\"darkgreen\", \"limegreen\", \"cornflowerblue\", \"blue\", \"indigo\"]\n", "colors_ana = [\"green\", \"greenyellow\", \"darkturquoise\", \"dodgerblue\", \"darkviolet\"]\n", "plt.figure()\n", "for i, conc in enumerate(output_list_mbt_v):\n", - " plt.plot(mbt_object.y, conc[-1, :, 75], color=colors_mbt[i], label=f\"Mibitrans a={parameter_list_v[i]}m\")\n", + " plt.plot(mbt_res_va.y, conc[-1, :, 75], color=colors_mbt[i], label=f\"Mibitrans a={parameter_list_v[i]}m\")\n", "for i, conc in enumerate(output_list_ana_v):\n", - " plt.plot(ana_object.y, conc[-1, :, 75], color=colors_ana[i], linestyle=\"--\",\n", + " plt.plot(ana_res_va.y, conc[-1, :, 75], color=colors_ana[i], linestyle=\"--\",\n", " label=f\"Anatrans a={parameter_list[i]}m\")\n", "plt.legend()\n", "plt.show()" - ] + ], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ], + "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + } + ], + "execution_count": 15 }, { "cell_type": "code", - "execution_count": null, "id": "f01c7d5012b64963", - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-16T13:49:04.784533Z", + "start_time": "2025-12-16T13:49:04.723620Z" + } + }, "source": [ "mbtv=output_list_mbt_v\n", "anav=output_list_ana_v\n", - "ani = animate_1d(x_axis_parameter=mbt_object.x,\n", + "ani = animate_1d(x_axis_parameter=mbt_res_va.x,\n", " y_axis_parameter=[mbtv[0][:,75,:], mbtv[1][:,75,:],mbtv[2][:,75,:],\n", " mbtv[3][:,75,:], mbtv[4][:,75,:],\n", " anav[0][:,75,:], anav[1][:,75,:], anav[2][:,75,:],\n", " anav[3][:,75,:], anav[4][:,75,:]],\n", - " time_parameter=mbt_object.t,\n", + " time_parameter=mbt_res_va.t,\n", " y_colors=[\"darkgreen\", \"limegreen\", \"cornflowerblue\", \"blue\", \"indigo\",\n", " \"green\", \"greenyellow\", \"darkturquoise\", \"dodgerblue\", \"violet\"],\n", " y_names=[\"Mibitrans a=0.1\", \"Mibitrans a=0.5\", \"Mibitrans a=1\", \"Mibitrans a=5\", \"Mibitrans a=10\",\n", @@ -336,23 +631,57 @@ "plt.xlabel(\"y-position [m]\")\n", "plt.xlim((0,350))\n", "plt.show()\n" - ] + ], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ], + "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + } + ], + "execution_count": 16 }, { "cell_type": "code", - "execution_count": null, "id": "4f70026152c8958e", - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-16T13:49:44.739679Z", + "start_time": "2025-12-16T13:49:44.406865Z" + } + }, "source": [ "mbtv=output_list_mbt_v\n", "anav=output_list_ana_v\n", - "ani = animate_1d(x_axis_parameter=mbt_object.y,\n", + "ani = animate_1d(x_axis_parameter=mbt_results.y,\n", " y_axis_parameter=[mbtv[0][:,:,50], mbtv[1][:,:,50], mbtv[2][:,:,50],\n", " mbtv[3][:,:,50], mbtv[4][:,:,50],\n", " anav[0][:,:,50], anav[1][:,:,50], anav[2][:,:,50],\n", " anav[3][:,:,50], anav[4][:,:,50]],\n", - " time_parameter=mbt_object.t,\n", + " time_parameter=mbt_res_va.t,\n", " y_colors=[\"darkgreen\", \"limegreen\", \"cornflowerblue\", \"blue\", \"indigo\",\n", " \"green\", \"greenyellow\", \"darkturquoise\", \"dodgerblue\", \"violet\"],\n", " y_names=[\"Mibitrans a=0.1\", \"Mibitrans a=0.5\", \"Mibitrans a=1\", \"Mibitrans a=5\", \"Mibitrans a=10\",\n", @@ -361,7 +690,38 @@ "plt.xlabel(\"y-position [m]\")\n", "\n", "plt.show()" - ] + ], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ], + "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + } + ], + "execution_count": 17 } ], "metadata": { diff --git a/mibitrans/transport/models.py b/mibitrans/transport/models.py index 6a14506..2473c6e 100644 --- a/mibitrans/transport/models.py +++ b/mibitrans/transport/models.py @@ -77,6 +77,16 @@ def __init__( Raises: TypeError : If input is not of the correct Dataclass. + Example:: + + obj = Mibitrans( + hydrological_parameters=HydrologicalParameters(), + attenuation_parameters=AttenuationParameters(), + source_parameters=SourceParameters(), + model_parameters=ModelParameters() + ) + results = obj.run() + """ super().__init__(hydrological_parameters, attenuation_parameters, source_parameters, model_parameters, verbose) @@ -394,9 +404,6 @@ def _calculate_concentration_for_all_xyt(self, xxx, yyy, ttt): return cxyt -# bioscreen ; Domenico - additional term - - class Bioscreen(Anatrans): """Model class using the analytical solution implemented in the BIOSCREEN screening model, Newell et al. (1997). From 5792fc72e1b772f7c7f5dce32c1425d542e3cf40 Mon Sep 17 00:00:00 2001 From: Bakker Date: Mon, 12 Jan 2026 11:53:41 +0100 Subject: [PATCH 02/19] Update example_keesler.ipynb --- examples/example_keesler.ipynb | 195 +++++++++++++++++++-------------- 1 file changed, 110 insertions(+), 85 deletions(-) diff --git a/examples/example_keesler.ipynb b/examples/example_keesler.ipynb index 3ac21dc..88be3a1 100644 --- a/examples/example_keesler.ipynb +++ b/examples/example_keesler.ipynb @@ -3,8 +3,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-12-18T13:08:14.462867Z", - "start_time": "2025-12-18T13:08:14.456126Z" + "end_time": "2026-01-05T10:42:03.534073Z", + "start_time": "2026-01-05T10:42:01.230065Z" } }, "cell_type": "code", @@ -15,7 +15,7 @@ ], "id": "63370b00e73a3f77", "outputs": [], - "execution_count": 31 + "execution_count": 1 }, { "metadata": {}, @@ -26,15 +26,21 @@ "The distribution of BIOSCREEN v1.4 is accompanied by an field site example of the Keesler Air Force Base in Mississippi, USA. As the data is sourced from the USA, units are mixed imperial and metric. For the modelling below, all parameters are converted to metric for consistency [1].\n", "\n", "#### Site description\n", - "In 1987, during their removal, multiple underground storage tanks (UST) leaked a mixture of BTEX and lead to the groundwater. Remediation efforts included a 'bioventing' system and an in well aeration system. Monitoring wells were used to evaluate natural attenuation [2,3]. Local geology is a fine- to medium-grained sand, underlain by a clay layer at 20ft depth. Bottom of sandy layer has local presence of peat. Thickness and continuity of clay layer are unknown, but not every boring showed the clay layer at 20ft. Assumption of clay continuous clay layer was made. Unconfined aquifer with groundwater levels varying between 5 and 9 ft below ground level. Groundwater flow is to the north-east, which eventually discharges into the Back Bay of Biloxi 2100ft downgradient [4]. Values of hydraulic gradient and conductivity vary, with values of 0.003 to 0.0083 ft/ft and 40, 61, 32 ft/day listed in [4]. In [1]. the values of 0.003 ft/ft and 0.011 cm/sec (31.2 ft/day) were used. Effective porosity is estimated to be 0.25 [4], resulting in groundwater velocity of 0.8 ft/day. Although [1] uses an effective porosity of 0.3, and results in groundwater velocity of 0.31 ft/day. For consistency, values reported in [1] are used in the modelling. Dispersivity values were based on estimated plume length of 280ft [1]. With 13.3, 1.3 and 0 for longitudinal, transverse horizontal and transverse vertical respectively. After calibration, these values were changed to 32.5, 3.25 and 0 respectively [1]." + "In 1987, during their removal, multiple underground storage tanks (UST) leaked a mixture of BTEX and lead to the groundwater. Remediation efforts included a 'bioventing' system and an in well aeration system. Monitoring wells were used to evaluate natural attenuation [2,3]. Local geology is a fine- to medium-grained sand, underlain by a clay layer at 20ft depth. Bottom of sandy layer has local presence of peat. Thickness and continuity of clay layer are unknown, but not every boring showed the clay layer at 20ft. Assumption of clay continuous clay layer was made. Unconfined aquifer with groundwater levels varying between 5 and 9 ft below ground level. Groundwater flow is to the north-east, which eventually discharges into the Back Bay of Biloxi, 2100ft downgradient [4]. Values of hydraulic gradient and conductivity vary, with values of 0.003 to 0.0083 ft/ft and 40, 61, 32 ft/day listed in [4]. In [1]. the values of 0.003 ft/ft and 0.011 cm/sec (31.2 ft/day) were used. Effective porosity is estimated to be 0.25 [4], resulting in groundwater velocity of 0.8 ft/day. Although [1] uses an effective porosity of 0.3, and results in groundwater velocity of 0.31 ft/day. For consistency, values reported in [1] are used in the modelling. Dispersivity values were based on estimated plume length of 280ft [1]. With 13.3ft, 1.3ft and 0ft for longitudinal, transverse horizontal and transverse vertical dispersivity respectively. After calibration, these values were changed to 32.5ft, 3.25ft and 0ft respectively [1]." ], "id": "ab077fc2fd5e825" }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "", + "id": "1d69d4356c2cc7f9" + }, { "metadata": { "ExecuteTime": { - "end_time": "2025-12-18T13:08:14.603830Z", - "start_time": "2025-12-18T13:08:14.593264Z" + "end_time": "2026-01-05T10:42:09.048493Z", + "start_time": "2026-01-05T10:42:09.042118Z" } }, "cell_type": "code", @@ -61,19 +67,19 @@ ] } ], - "execution_count": 32 + "execution_count": 2 }, { "metadata": {}, "cell_type": "markdown", - "source": "The fraction of organic carbon is 0.000057 based of lab analysis, soil bulk density is estimated to be 1.7 (kg/L) [1]. Partition coefficient of BTEX differs by order of magnitude (38, 135, 95, 240 L/kg for B, T, E, X), [1] uses the value of benzene; 38 L/kg. Electron acceptor concentrations are based on groundwater sampling performed in 1995. For oxygen, nitrate and sulfate, the difference between background concentration in the aquifer and minimum concentrations in the plume are used. For ferrous iron and methane, average concentrations in the plume area are used. Values differ somewhat from those report in [4] (elaborate?).", + "source": "The fraction of organic carbon is 0.000057 based of lab analysis, soil bulk density is estimated to be 1.7 (kg/L) [1]. Partition coefficient of BTEX differs by order of magnitude (38, 135, 95, 240 L/kg for B, T, E, X), [1] uses the value of benzene; 38 L/kg. Electron acceptor concentrations are based on groundwater sampling performed in 1995. For oxygen, nitrate and sulfate, the difference between background concentration in the aquifer and minimum concentrations in the plume are used. For ferrous iron and methane, average concentrations in the plume area are used. Values differ somewhat from those reported in [4] (elaborate?).", "id": "bb52ab633659cab4" }, { "metadata": { "ExecuteTime": { - "end_time": "2025-12-18T13:08:14.694489Z", - "start_time": "2025-12-18T13:08:14.684650Z" + "end_time": "2026-01-05T10:42:13.021724Z", + "start_time": "2026-01-05T10:42:13.015011Z" } }, "cell_type": "code", @@ -105,7 +111,7 @@ ] } ], - "execution_count": 33 + "execution_count": 3 }, { "metadata": {}, @@ -116,8 +122,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-12-18T13:08:14.835226Z", - "start_time": "2025-12-18T13:08:14.827151Z" + "end_time": "2026-01-05T10:42:14.792987Z", + "start_time": "2026-01-05T10:42:14.789445Z" } }, "cell_type": "code", @@ -132,19 +138,19 @@ ], "id": "72fbc1542ca07cd0", "outputs": [], - "execution_count": 34 + "execution_count": 4 }, { "metadata": {}, "cell_type": "markdown", - "source": "Model dimensions used for the BIOSCREEN model are; length = 320ft, width = 200ft, duration=6y [1]. Model resolution is always a fraction of model dimensions, 1/10 for length, 1/4 for width and 1/10 for time.", + "source": "Model dimensions used for the BIOSCREEN model are; length = 320ft, width = 200ft, duration=6y [1]. In BIOSCREEN resolution is always a fraction of model dimensions, 1/10 for length, 1/4 for width and 1/10 for time. For the mibitrans package, model resolution is freely changeable", "id": "bcf58154d070a76f" }, { "metadata": { "ExecuteTime": { - "end_time": "2025-12-18T13:08:14.919142Z", - "start_time": "2025-12-18T13:08:14.910225Z" + "end_time": "2026-01-05T12:12:09.118473Z", + "start_time": "2026-01-05T12:12:09.114433Z" } }, "cell_type": "code", @@ -158,13 +164,23 @@ ], "id": "b0215d655ce763b5", "outputs": [], - "execution_count": 35 + "execution_count": 28 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "#### Models\n", + "\n", + "With the model parameters entered, the model is run with the three models implemented in mibitrans. The Bioscreen model class performs the same calculations as those implemented in BIOSCREEN. The Anatrans model class uses the untruncated form of the Bioscreen model, the additional term increases accuracy for small times and distances. The Mibitrans model class uses an analytical solution based on Wexler (1992). For further details about each analytical model, see the 'example_walkthrough' notebook and the theory that accompany this package." + ], + "id": "ac1f32016fb4d14f" }, { "metadata": { "ExecuteTime": { - "end_time": "2025-12-18T13:08:15.143295Z", - "start_time": "2025-12-18T13:08:15.058451Z" + "end_time": "2026-01-05T12:12:10.765092Z", + "start_time": "2026-01-05T12:12:10.694495Z" } }, "cell_type": "code", @@ -175,30 +191,17 @@ " model_parameters=model_pars,)\n", "bio_results = bio_model.run()\n", "bio_results.centerline()\n", - "plt.show()\n", - "print(bio_results.attenuation_parameters.retardation)\n", - "print(bio_results.rv)\n", - "print(bio_results.hydrological_parameters.velocity)\n" + "plt.show()\n" ], "id": "1fd5d083a23264c5", - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1.012274\n", - "0.09388762331147496\n", - "0.09504\n" - ] - } - ], - "execution_count": 36 + "outputs": [], + "execution_count": 29 }, { "metadata": { "ExecuteTime": { - "end_time": "2025-12-18T13:08:15.656797Z", - "start_time": "2025-12-18T13:08:15.575943Z" + "end_time": "2026-01-05T12:12:12.942467Z", + "start_time": "2026-01-05T12:12:12.872525Z" } }, "cell_type": "code", @@ -213,13 +216,13 @@ ], "id": "bfe447c47e49d8e6", "outputs": [], - "execution_count": 37 + "execution_count": 30 }, { "metadata": { "ExecuteTime": { - "end_time": "2025-12-18T13:08:21.651217Z", - "start_time": "2025-12-18T13:08:16.016435Z" + "end_time": "2026-01-05T12:12:20.154601Z", + "start_time": "2026-01-05T12:12:15.632852Z" } }, "cell_type": "code", @@ -234,13 +237,19 @@ ], "id": "451b06b489120021", "outputs": [], - "execution_count": 38 + "execution_count": 31 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Comparing the three models at the first time step shows that the Bioscreen model underestimates the source concentrations. The Anatrans and Mibitrans models start at the correct source zone concentration, but show different behaviour along the domain.", + "id": "a680dad770bc67b4" }, { "metadata": { "ExecuteTime": { - "end_time": "2025-12-18T13:08:22.053224Z", - "start_time": "2025-12-18T13:08:22.028450Z" + "end_time": "2026-01-05T11:55:54.823919Z", + "start_time": "2026-01-05T11:55:54.734176Z" } }, "cell_type": "code", @@ -249,30 +258,54 @@ "mbt_results.centerline(time=time_point, label=\"mbt\", color=\"blue\")\n", "ana_results.centerline(time=time_point, label=\"ana\", color=\"red\", linestyle=\"--\")\n", "bio_results.centerline(time=time_point, label=\"bio\", color=\"green\", linestyle=\":\")\n", + "plt.xlim((-5,100)) # Reduce the x-axis limit to better observe differences\n", "plt.show()" ], "id": "4d1b4436f0aa185b", - "outputs": [], - "execution_count": 39 + "outputs": [ + { + "data": { + "text/plain": [ + "
" + ], + "image/png": "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" + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + } + ], + "execution_count": 25 }, { "metadata": { "ExecuteTime": { - "end_time": "2025-12-18T13:08:22.280579Z", - "start_time": "2025-12-18T13:08:22.277190Z" + "end_time": "2026-01-05T12:02:37.090326Z", + "start_time": "2026-01-05T12:02:37.068247Z" } }, "cell_type": "code", - "source": "%matplotlib notebook", + "source": [ + "# Needed to show animations in Jupyter Notebook\n", + "%matplotlib notebook" + ], "id": "ad0e7d335620f2c2", "outputs": [], - "execution_count": 40 + "execution_count": 26 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Over time, the source concentration for the Bioscreen model increases to the correct value, and the results resolve to that of the Anatrans model. Differences between the Anatrans and Mibitrans model decrease", + "id": "d145f437f66d2c5d" }, { "metadata": { "ExecuteTime": { - "end_time": "2025-12-18T13:08:22.385383Z", - "start_time": "2025-12-18T13:08:22.309886Z" + "end_time": "2026-01-05T12:12:27.502301Z", + "start_time": "2026-01-05T12:12:27.475536Z" } }, "cell_type": "code", @@ -301,7 +334,7 @@ "" ], "text/html": [ - "
" + "
" ] }, "metadata": {}, @@ -309,23 +342,15 @@ "jetTransient": { "display_id": null } - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "C:\\Users\\4662504\\Documents\\Projects\\mibitrans\\.venv\\Lib\\site-packages\\matplotlib\\animation.py:908: UserWarning: Animation was deleted without rendering anything. This is most likely not intended. To prevent deletion, assign the Animation to a variable, e.g. `anim`, that exists until you output the Animation using `plt.show()` or `anim.save()`.\n", - " warnings.warn(\n" - ] } ], - "execution_count": 41 + "execution_count": 32 }, { "metadata": { "ExecuteTime": { - "end_time": "2025-12-18T13:08:26.940499Z", - "start_time": "2025-12-18T13:08:22.459199Z" + "end_time": "2026-01-05T10:40:55.465932900Z", + "start_time": "2025-12-18T14:03:42.062559Z" } }, "cell_type": "code", @@ -339,13 +364,13 @@ ], "id": "d7f2a3f0f078a615", "outputs": [], - "execution_count": 42 + "execution_count": 57 }, { "metadata": { "ExecuteTime": { - "end_time": "2025-12-18T13:08:27.206056Z", - "start_time": "2025-12-18T13:08:27.178002Z" + "end_time": "2026-01-05T10:40:55.465932900Z", + "start_time": "2025-12-18T14:03:48.389239Z" } }, "cell_type": "code", @@ -374,7 +399,7 @@ "" ], "text/html": [ - "
" + "
" ] }, "metadata": {}, @@ -384,13 +409,13 @@ } } ], - "execution_count": 43 + "execution_count": 58 }, { "metadata": { "ExecuteTime": { - "end_time": "2025-12-18T13:08:27.387237Z", - "start_time": "2025-12-18T13:08:27.360869Z" + "end_time": "2026-01-05T10:40:55.471696900Z", + "start_time": "2025-12-18T14:03:48.485657Z" } }, "cell_type": "code", @@ -429,7 +454,7 @@ "" ], "text/html": [ - "
" + "
" ] }, "metadata": {}, @@ -439,7 +464,7 @@ } } ], - "execution_count": 44 + "execution_count": 59 }, { "metadata": {}, @@ -450,11 +475,19 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-12-18T13:08:27.428314Z", - "start_time": "2025-12-18T13:08:27.403954Z" + "end_time": "2026-01-05T10:40:55.471696900Z", + "start_time": "2025-12-18T14:03:48.532444Z" } }, "cell_type": "code", + "source": [ + "plt.figure()\n", + "plt.plot(mbt_results_instant.x, mbt_results_instant.cxyt_noBC[-1,100,:])\n", + "plt.plot(ana_results_instant.x, ana_results_instant.cxyt_noBC[-1, 100, :])\n", + "plt.plot(bio_results_instant.x, bio_results_instant.cxyt_noBC[-1, 100, :])\n", + "plt.show()" + ], + "id": "5ec0210f2f0340ab", "outputs": [ { "data": { @@ -475,7 +508,7 @@ "" ], "text/html": [ - "
" + "
" ] }, "metadata": {}, @@ -485,15 +518,7 @@ } } ], - "execution_count": 45, - "source": [ - "plt.figure()\n", - "plt.plot(mbt_results_instant.x, mbt_results_instant.cxyt_noBC[-1,100,:])\n", - "plt.plot(ana_results_instant.x, ana_results_instant.cxyt_noBC[-1, 100, :])\n", - "plt.plot(bio_results_instant.x, bio_results_instant.cxyt_noBC[-1, 100, :])\n", - "plt.show()" - ], - "id": "5ec0210f2f0340ab" + "execution_count": 60 }, { "metadata": {}, From f8b9f895f07347f9e8aa1c32b4433c9c802e39d2 Mon Sep 17 00:00:00 2001 From: Bakker Date: Wed, 14 Jan 2026 11:47:05 +0100 Subject: [PATCH 03/19] Commit to be able to apply stashed changes --- examples/example_keesler.ipynb | 455 +++++------------ examples/example_kohler.ipynb | 306 ++--------- ...xample_mibitrans_anatrans_comparison.ipynb | 480 +++--------------- 3 files changed, 240 insertions(+), 1001 deletions(-) diff --git a/examples/example_keesler.ipynb b/examples/example_keesler.ipynb index 88be3a1..d40ccd3 100644 --- a/examples/example_keesler.ipynb +++ b/examples/example_keesler.ipynb @@ -1,25 +1,21 @@ { "cells": [ { - "metadata": { - "ExecuteTime": { - "end_time": "2026-01-05T10:42:03.534073Z", - "start_time": "2026-01-05T10:42:01.230065Z" - } - }, "cell_type": "code", + "execution_count": null, + "id": "63370b00e73a3f77", + "metadata": {}, + "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import mibitrans as mbt\n", "import numpy as np" - ], - "id": "63370b00e73a3f77", - "outputs": [], - "execution_count": 1 + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "ab077fc2fd5e825", + "metadata": {}, "source": [ "### Field site example: Keesler Air-Force Base\n", "\n", @@ -27,23 +23,20 @@ "\n", "#### Site description\n", "In 1987, during their removal, multiple underground storage tanks (UST) leaked a mixture of BTEX and lead to the groundwater. Remediation efforts included a 'bioventing' system and an in well aeration system. Monitoring wells were used to evaluate natural attenuation [2,3]. Local geology is a fine- to medium-grained sand, underlain by a clay layer at 20ft depth. Bottom of sandy layer has local presence of peat. Thickness and continuity of clay layer are unknown, but not every boring showed the clay layer at 20ft. Assumption of clay continuous clay layer was made. Unconfined aquifer with groundwater levels varying between 5 and 9 ft below ground level. Groundwater flow is to the north-east, which eventually discharges into the Back Bay of Biloxi, 2100ft downgradient [4]. Values of hydraulic gradient and conductivity vary, with values of 0.003 to 0.0083 ft/ft and 40, 61, 32 ft/day listed in [4]. In [1]. the values of 0.003 ft/ft and 0.011 cm/sec (31.2 ft/day) were used. Effective porosity is estimated to be 0.25 [4], resulting in groundwater velocity of 0.8 ft/day. Although [1] uses an effective porosity of 0.3, and results in groundwater velocity of 0.31 ft/day. For consistency, values reported in [1] are used in the modelling. Dispersivity values were based on estimated plume length of 280ft [1]. With 13.3ft, 1.3ft and 0ft for longitudinal, transverse horizontal and transverse vertical dispersivity respectively. After calibration, these values were changed to 32.5ft, 3.25ft and 0ft respectively [1]." - ], - "id": "ab077fc2fd5e825" + ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "", - "id": "1d69d4356c2cc7f9" + "id": "1d69d4356c2cc7f9", + "metadata": {}, + "source": [] }, { - "metadata": { - "ExecuteTime": { - "end_time": "2026-01-05T10:42:09.048493Z", - "start_time": "2026-01-05T10:42:09.042118Z" - } - }, "cell_type": "code", + "execution_count": null, + "id": "d03bc12d32df84ac", + "metadata": {}, + "outputs": [], "source": [ "ft = 3.281 #Conversion factor ft/m\n", "\n", @@ -56,33 +49,22 @@ " alpha_z = 0\n", ")\n", "print(hydro_pars.velocity)" - ], - "id": "d03bc12d32df84ac", - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.09504\n" - ] - } - ], - "execution_count": 2 + ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "The fraction of organic carbon is 0.000057 based of lab analysis, soil bulk density is estimated to be 1.7 (kg/L) [1]. Partition coefficient of BTEX differs by order of magnitude (38, 135, 95, 240 L/kg for B, T, E, X), [1] uses the value of benzene; 38 L/kg. Electron acceptor concentrations are based on groundwater sampling performed in 1995. For oxygen, nitrate and sulfate, the difference between background concentration in the aquifer and minimum concentrations in the plume are used. For ferrous iron and methane, average concentrations in the plume area are used. Values differ somewhat from those reported in [4] (elaborate?).", - "id": "bb52ab633659cab4" + "id": "bb52ab633659cab4", + "metadata": {}, + "source": [ + "The fraction of organic carbon is 0.000057 based of lab analysis, soil bulk density is estimated to be 1.7 (kg/L) [1]. Partition coefficient of BTEX differs by order of magnitude (38, 135, 95, 240 L/kg for B, T, E, X), [1] uses the value of benzene; 38 L/kg. Electron acceptor concentrations are based on groundwater sampling performed in 1995. For oxygen, nitrate and sulfate, the difference between background concentration in the aquifer and minimum concentrations in the plume are used. For ferrous iron and methane, average concentrations in the plume area are used. Values differ somewhat from those reported in [4] (elaborate?)." + ] }, { - "metadata": { - "ExecuteTime": { - "end_time": "2026-01-05T10:42:13.021724Z", - "start_time": "2026-01-05T10:42:13.015011Z" - } - }, "cell_type": "code", + "execution_count": null, + "id": "6688d6a2be9895a8", + "metadata": {}, + "outputs": [], "source": [ "att_pars = mbt.AttenuationParameters(\n", " bulk_density=1.7, # kg/L (note; for consistency, should use g/m3, but since units cancel out here for calculation, using more intuitive kg/L instead)\n", @@ -100,33 +82,22 @@ " delta_sulfate=26.2 - 3.8, #background conc - minimum conc, [g/m3]\n", " methane=6.6, #average conc, [g/m3]\n", ")" - ], - "id": "6688d6a2be9895a8", - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Retardation based on input values = 1.012274\n" - ] - } - ], - "execution_count": 3 + ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "Conditions and parameters of the contaminant source were inferred from BTEX monitoring data and geologic logs [1]. The source thickness is estimated to be 10ft, with a total 2000kg of pure BTEX. Total source width of 130ft, with 28ft zone of 0.057 g/m3, 30ft zone of 2.508 g/m3, 14ft zone of 13.68, 30ft zone of 2.508 g/m3, 28ft zone of 0.057 g/m3.", - "id": "a5c180d376ce7795" + "id": "a5c180d376ce7795", + "metadata": {}, + "source": [ + "Conditions and parameters of the contaminant source were inferred from BTEX monitoring data and geologic logs [1]. The source thickness is estimated to be 10ft, with a total 2000kg of pure BTEX. Total source width of 130ft, with 28ft zone of 0.057 g/m3, 30ft zone of 2.508 g/m3, 14ft zone of 13.68, 30ft zone of 2.508 g/m3, 28ft zone of 0.057 g/m3." + ] }, { - "metadata": { - "ExecuteTime": { - "end_time": "2026-01-05T10:42:14.792987Z", - "start_time": "2026-01-05T10:42:14.789445Z" - } - }, "cell_type": "code", + "execution_count": null, + "id": "72fbc1542ca07cd0", + "metadata": {}, + "outputs": [], "source": [ "source_pars = mbt.SourceParameters(\n", " # Note; as plume is symmetric, source is described from center outwards in y-direction.\n", @@ -135,25 +106,22 @@ " depth=10/ft, #[m]\n", " total_mass=2000000, #[g]\n", ")" - ], - "id": "72fbc1542ca07cd0", - "outputs": [], - "execution_count": 4 + ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "Model dimensions used for the BIOSCREEN model are; length = 320ft, width = 200ft, duration=6y [1]. In BIOSCREEN resolution is always a fraction of model dimensions, 1/10 for length, 1/4 for width and 1/10 for time. For the mibitrans package, model resolution is freely changeable", - "id": "bcf58154d070a76f" + "id": "bcf58154d070a76f", + "metadata": {}, + "source": [ + "Model dimensions used for the BIOSCREEN model are; length = 320ft, width = 200ft, duration=6y [1]. In BIOSCREEN resolution is always a fraction of model dimensions, 1/10 for length, 1/4 for width and 1/10 for time. For the mibitrans package, model resolution is freely changeable" + ] }, { - "metadata": { - "ExecuteTime": { - "end_time": "2026-01-05T12:12:09.118473Z", - "start_time": "2026-01-05T12:12:09.114433Z" - } - }, "cell_type": "code", + "execution_count": null, + "id": "b0215d655ce763b5", + "metadata": {}, + "outputs": [], "source": [ "model_pars = mbt.ModelParameters(model_length=1000/ft,\n", " model_width=200/ft,\n", @@ -161,29 +129,24 @@ " dx=5/ft,\n", " dy=1/ft,\n", " dt=365/5)" - ], - "id": "b0215d655ce763b5", - "outputs": [], - "execution_count": 28 + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "ac1f32016fb4d14f", + "metadata": {}, "source": [ "#### Models\n", "\n", "With the model parameters entered, the model is run with the three models implemented in mibitrans. The Bioscreen model class performs the same calculations as those implemented in BIOSCREEN. The Anatrans model class uses the untruncated form of the Bioscreen model, the additional term increases accuracy for small times and distances. The Mibitrans model class uses an analytical solution based on Wexler (1992). For further details about each analytical model, see the 'example_walkthrough' notebook and the theory that accompany this package." - ], - "id": "ac1f32016fb4d14f" + ] }, { - "metadata": { - "ExecuteTime": { - "end_time": "2026-01-05T12:12:10.765092Z", - "start_time": "2026-01-05T12:12:10.694495Z" - } - }, "cell_type": "code", + "execution_count": null, + "id": "1fd5d083a23264c5", + "metadata": {}, + "outputs": [], "source": [ "bio_model = mbt.Bioscreen(hydrological_parameters=hydro_pars,\n", " attenuation_parameters=att_pars,\n", @@ -192,19 +155,14 @@ "bio_results = bio_model.run()\n", "bio_results.centerline()\n", "plt.show()\n" - ], - "id": "1fd5d083a23264c5", - "outputs": [], - "execution_count": 29 + ] }, { - "metadata": { - "ExecuteTime": { - "end_time": "2026-01-05T12:12:12.942467Z", - "start_time": "2026-01-05T12:12:12.872525Z" - } - }, "cell_type": "code", + "execution_count": null, + "id": "bfe447c47e49d8e6", + "metadata": {}, + "outputs": [], "source": [ "ana_model = mbt.Anatrans(hydrological_parameters=hydro_pars,\n", " attenuation_parameters=att_pars,\n", @@ -213,19 +171,14 @@ "ana_results = ana_model.run()\n", "ana_results.centerline()\n", "plt.show()" - ], - "id": "bfe447c47e49d8e6", - "outputs": [], - "execution_count": 30 + ] }, { - "metadata": { - "ExecuteTime": { - "end_time": "2026-01-05T12:12:20.154601Z", - "start_time": "2026-01-05T12:12:15.632852Z" - } - }, "cell_type": "code", + "execution_count": null, + "id": "451b06b489120021", + "metadata": {}, + "outputs": [], "source": [ "mbt_model = mbt.Mibitrans(hydrological_parameters=hydro_pars,\n", " attenuation_parameters=att_pars,\n", @@ -234,126 +187,68 @@ "mbt_results =mbt_model.run()\n", "mbt_results.centerline()\n", "plt.show()" - ], - "id": "451b06b489120021", - "outputs": [], - "execution_count": 31 + ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "Comparing the three models at the first time step shows that the Bioscreen model underestimates the source concentrations. The Anatrans and Mibitrans models start at the correct source zone concentration, but show different behaviour along the domain.", - "id": "a680dad770bc67b4" + "id": "a680dad770bc67b4", + "metadata": {}, + "source": [ + "Comparing the three models at the first time step shows that the Bioscreen model underestimates the source concentrations. The Anatrans and Mibitrans models start at the correct source zone concentration, but show different behaviour along the domain." + ] }, { - "metadata": { - "ExecuteTime": { - "end_time": "2026-01-05T11:55:54.823919Z", - "start_time": "2026-01-05T11:55:54.734176Z" - } - }, "cell_type": "code", + "execution_count": null, + "id": "4d1b4436f0aa185b", + "metadata": {}, + "outputs": [], "source": [ "time_point = 365/5\n", "mbt_results.centerline(time=time_point, label=\"mbt\", color=\"blue\")\n", "ana_results.centerline(time=time_point, label=\"ana\", color=\"red\", linestyle=\"--\")\n", "bio_results.centerline(time=time_point, label=\"bio\", color=\"green\", linestyle=\":\")\n", "plt.xlim((-5,100)) # Reduce the x-axis limit to better observe differences\n", + "plt.legend()\n", "plt.show()" - ], - "id": "4d1b4436f0aa185b", - "outputs": [ - { - "data": { - "text/plain": [ - "
" - ], - "image/png": "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" - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - } - ], - "execution_count": 25 + ] }, { - "metadata": { - "ExecuteTime": { - "end_time": "2026-01-05T12:02:37.090326Z", - "start_time": "2026-01-05T12:02:37.068247Z" - } - }, "cell_type": "code", + "execution_count": null, + "id": "ad0e7d335620f2c2", + "metadata": {}, + "outputs": [], "source": [ "# Needed to show animations in Jupyter Notebook\n", "%matplotlib notebook" - ], - "id": "ad0e7d335620f2c2", - "outputs": [], - "execution_count": 26 + ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "Over time, the source concentration for the Bioscreen model increases to the correct value, and the results resolve to that of the Anatrans model. Differences between the Anatrans and Mibitrans model decrease", - "id": "d145f437f66d2c5d" + "id": "d145f437f66d2c5d", + "metadata": {}, + "source": [ + "Over time, the source concentration for the Bioscreen model increases to the correct value, and the results resolve to that of the Anatrans model. Differences between the Anatrans and Mibitrans model decrease" + ] }, { - "metadata": { - "ExecuteTime": { - "end_time": "2026-01-05T12:12:27.502301Z", - "start_time": "2026-01-05T12:12:27.475536Z" - } - }, "cell_type": "code", + "execution_count": null, + "id": "b3887af76478a217", + "metadata": {}, + "outputs": [], "source": [ "anim = mbt.centerline([mbt_results, ana_results, bio_results], legend_names=[\"mbt\", \"ana\", \"bio\"], animate=True)\n", "plt.show()" - ], - "id": "b3887af76478a217", - "outputs": [ - { - "data": { - "text/plain": [ - "" - ], - "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - }, - { - "data": { - "text/plain": [ - "" - ], - "text/html": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - } - ], - "execution_count": 32 + ] }, { - "metadata": { - "ExecuteTime": { - "end_time": "2026-01-05T10:40:55.465932900Z", - "start_time": "2025-12-18T14:03:42.062559Z" - } - }, "cell_type": "code", + "execution_count": null, + "id": "d7f2a3f0f078a615", + "metadata": {}, + "outputs": [], "source": [ "bio_model.instant_reaction(electron_acceptors=electron_acceptor_concentrations)\n", "ana_model.instant_reaction(electron_acceptors=electron_acceptor_concentrations)\n", @@ -361,64 +256,25 @@ "bio_results_instant = bio_model.run()\n", "ana_results_instant = ana_model.run()\n", "mbt_results_instant = mbt_model.run()" - ], - "id": "d7f2a3f0f078a615", - "outputs": [], - "execution_count": 57 + ] }, { - "metadata": { - "ExecuteTime": { - "end_time": "2026-01-05T10:40:55.465932900Z", - "start_time": "2025-12-18T14:03:48.389239Z" - } - }, "cell_type": "code", + "execution_count": null, + "id": "e2c45a51f2c472d3", + "metadata": {}, + "outputs": [], "source": [ "anim = mbt.centerline([mbt_results_instant, ana_results_instant, bio_results_instant], legend_names=[\"mbt\", \"ana\", \"bio\"], animate=True)\n", "plt.show()" - ], - "id": "e2c45a51f2c472d3", - "outputs": [ - { - "data": { - "text/plain": [ - "" - ], - "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - }, - { - "data": { - "text/plain": [ - "" - ], - "text/html": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - } - ], - "execution_count": 58 + ] }, { - "metadata": { - "ExecuteTime": { - "end_time": "2026-01-05T10:40:55.471696900Z", - "start_time": "2025-12-18T14:03:48.485657Z" - } - }, "cell_type": "code", + "execution_count": null, + "id": "6ec97664c7c1f918", + "metadata": {}, + "outputs": [], "source": [ "time_point = 365 * 5\n", "plt.figure()\n", @@ -432,97 +288,34 @@ "plt.scatter(field_data_x, field_data_c, color=\"black\")\n", "plt.xlim(0,150)\n", "plt.show()" - ], - "id": "6ec97664c7c1f918", - "outputs": [ - { - "data": { - "text/plain": [ - "" - ], - "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - }, - { - "data": { - "text/plain": [ - "" - ], - "text/html": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - } - ], - "execution_count": 59 + ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "According to Bioscreen, the above model predicts the measured field concentrations fairly well", - "id": "eb41bc730334f094" + "id": "eb41bc730334f094", + "metadata": {}, + "source": [ + "According to Bioscreen, the above model predicts the measured field concentrations fairly well" + ] }, { - "metadata": { - "ExecuteTime": { - "end_time": "2026-01-05T10:40:55.471696900Z", - "start_time": "2025-12-18T14:03:48.532444Z" - } - }, "cell_type": "code", + "execution_count": null, + "id": "5ec0210f2f0340ab", + "metadata": {}, + "outputs": [], "source": [ "plt.figure()\n", "plt.plot(mbt_results_instant.x, mbt_results_instant.cxyt_noBC[-1,100,:])\n", "plt.plot(ana_results_instant.x, ana_results_instant.cxyt_noBC[-1, 100, :])\n", "plt.plot(bio_results_instant.x, bio_results_instant.cxyt_noBC[-1, 100, :])\n", "plt.show()" - ], - "id": "5ec0210f2f0340ab", - "outputs": [ - { - "data": { - "text/plain": [ - "" - ], - "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - }, - { - "data": { - "text/plain": [ - "" - ], - "text/html": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - } - ], - "execution_count": 60 + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "eb969db6ab0c1424", + "metadata": {}, "source": [ "(1) Newell, C. J., McLeod, R. K., & Gonzales, J. R. (1997). BIOSCREEN natural attenuation decision support\n", " system version 1.4 revisions, Tech. rep., U.S. EPA.\n", @@ -532,14 +325,15 @@ "(3) COST AND PERFORMANCE CASE STUDY REPORT - Keesler Air Force Base Base Exchange Service Station (make actual citation)\n", "\n", "(4) Corrective action plan for the risk-based closure of the base exchange service station, area of concern - A (ST-06) Keesler Air Froce Base, Mississippi (make actual citation)\n" - ], - "id": "eb969db6ab0c1424" + ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "https://www.sunherald.com/news/local/military/article234197522.html", - "id": "a95d32a3185dea0" + "id": "a95d32a3185dea0", + "metadata": {}, + "source": [ + "https://www.sunherald.com/news/local/military/article234197522.html" + ] } ], "metadata": { @@ -557,8 +351,7 @@ "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.6" + "pygments_lexer": "ipython2" } }, "nbformat": 4, diff --git a/examples/example_kohler.ipynb b/examples/example_kohler.ipynb index 90584e7..f465c5e 100644 --- a/examples/example_kohler.ipynb +++ b/examples/example_kohler.ipynb @@ -1,13 +1,11 @@ { "cells": [ { - "metadata": { - "ExecuteTime": { - "end_time": "2025-12-16T14:10:01.500184Z", - "start_time": "2025-12-16T14:10:01.495897Z" - } - }, "cell_type": "code", + "execution_count": null, + "id": "ac421af9f5d936aa", + "metadata": {}, + "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import numpy as np\n", @@ -21,25 +19,22 @@ "from mibitrans.transport.models import Anatrans\n", "from mibitrans.transport.models import Mibitrans\n", "from mibitrans.visualize.plot_line import centerline" - ], - "id": "ac421af9f5d936aa", - "outputs": [], - "execution_count": 22 + ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "Note: For comparison to steady state, value of flow velocity does not matter, since it only determines how fast steady state will be accomplished.", - "id": "524f38b7b357f749" + "id": "524f38b7b357f749", + "metadata": {}, + "source": [ + "Note: For comparison to steady state, value of flow velocity does not matter, since it only determines how fast steady state will be accomplished." + ] }, { - "metadata": { - "ExecuteTime": { - "end_time": "2025-12-16T14:10:59.438453Z", - "start_time": "2025-12-16T14:10:59.433389Z" - } - }, "cell_type": "code", + "execution_count": null, + "id": "8734f631ad0baf94", + "metadata": {}, + "outputs": [], "source": [ "hydro = HydrologicalParameters(\n", " velocity=0.1,\n", @@ -66,288 +61,100 @@ " dy=0.1,\n", " dt=365 / 5,\n", ")" - ], - "id": "8734f631ad0baf94", - "outputs": [], - "execution_count": 24 + ] }, { - "metadata": { - "ExecuteTime": { - "end_time": "2025-12-16T14:11:01.496158Z", - "start_time": "2025-12-16T14:11:01.321526Z" - } - }, "cell_type": "code", + "execution_count": null, + "id": "23b7c98fb94ec32a", + "metadata": {}, + "outputs": [], "source": [ "ana_obj = Anatrans(hydro, att, source, model)\n", "ana_obj.instant_reaction(ElectronAcceptors(8, 0, 0, 0, 0), UtilizationFactor(3.5,1,1,1,1))\n", "results_ana = ana_obj.run()" - ], - "id": "23b7c98fb94ec32a", - "outputs": [], - "execution_count": 25 + ] }, { - "metadata": { - "ExecuteTime": { - "end_time": "2025-12-16T14:11:03.330927Z", - "start_time": "2025-12-16T14:11:03.323421Z" - } - }, "cell_type": "code", + "execution_count": null, + "id": "ecaadf8f8cbc3343", + "metadata": {}, + "outputs": [], "source": [ "# Needed to show animations in Jupyter Notebooks\n", "%matplotlib notebook" - ], - "id": "ecaadf8f8cbc3343", - "outputs": [], - "execution_count": 26 + ] }, { - "metadata": { - "ExecuteTime": { - "end_time": "2025-12-16T14:11:04.863065Z", - "start_time": "2025-12-16T14:11:04.832695Z" - } - }, "cell_type": "code", + "execution_count": null, + "id": "a17c9965f5dadfc0", + "metadata": {}, + "outputs": [], "source": [ "anim = results_ana.centerline(animate=True)\n", "plt.show()" - ], - "id": "a17c9965f5dadfc0", - "outputs": [ - { - "data": { - "text/plain": [ - "" - ], - "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - }, - { - "data": { - "text/plain": [ - "" - ], - "text/html": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - } - ], - "execution_count": 27 + ] }, { - "metadata": { - "ExecuteTime": { - "end_time": "2025-12-16T14:11:22.700774Z", - "start_time": "2025-12-16T14:11:22.555055Z" - } - }, "cell_type": "code", + "execution_count": null, + "id": "40d59891433008e2", + "metadata": {}, + "outputs": [], "source": [ "anim = results_ana.plume_3d(animate=True, cmap=\"viridis\")\n", "plt.show()" - ], - "id": "40d59891433008e2", - "outputs": [ - { - "data": { - "text/plain": [ - "" - ], - "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - }, - { - "data": { - "text/plain": [ - "" - ], - "text/html": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - } - ], - "execution_count": 28 + ] }, { - "metadata": { - "ExecuteTime": { - "end_time": "2025-12-16T14:11:48.713513Z", - "start_time": "2025-12-16T14:11:36.237823Z" - } - }, "cell_type": "code", + "execution_count": null, + "id": "35cc9be16b364665", + "metadata": {}, + "outputs": [], "source": [ "mbt_obj = Mibitrans(hydro, att, source, model)\n", "mbt_obj.instant_reaction(ElectronAcceptors(8, 0, 0, 0, 0), UtilizationFactor(3.5,1,1,1,1))\n", "results_mbt = mbt_obj.run()" - ], - "id": "35cc9be16b364665", - "outputs": [], - "execution_count": 29 + ] }, { - "metadata": { - "ExecuteTime": { - "end_time": "2025-12-16T14:11:50.444698Z", - "start_time": "2025-12-16T14:11:50.420644Z" - } - }, "cell_type": "code", + "execution_count": null, + "id": "10ce7f57ea8be225", + "metadata": {}, + "outputs": [], "source": [ "anim = results_mbt.centerline(animate=True)\n", "plt.show()" - ], - "id": "10ce7f57ea8be225", - "outputs": [ - { - "data": { - "text/plain": [ - "" - ], - "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - }, - { - "data": { - "text/plain": [ - "" - ], - "text/html": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - } - ], - "execution_count": 30 + ] }, { - "metadata": { - "ExecuteTime": { - "end_time": "2025-12-16T14:12:08.750578Z", - "start_time": "2025-12-16T14:12:08.712444Z" - } - }, "cell_type": "code", + "execution_count": null, + "id": "2b161f395cf35c99", + "metadata": {}, + "outputs": [], "source": [ "plt.figure()\n", "results_ana.centerline(color=\"orange\", label=\"anatrans\")\n", "results_mbt.centerline(color=\"green\", label=\"mibitrans\", linestyle=\"--\")\n", "plt.legend()\n", "plt.show()" - ], - "id": "2b161f395cf35c99", - "outputs": [ - { - "data": { - "text/plain": [ - "" - ], - "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - }, - { - "data": { - "text/plain": [ - "" - ], - "text/html": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - } - ], - "execution_count": 31 + ] }, { - "metadata": { - "ExecuteTime": { - "end_time": "2025-12-16T14:12:16.082421Z", - "start_time": "2025-12-16T14:12:16.055593Z" - } - }, "cell_type": "code", + "execution_count": null, + "id": "c4e911cc72a4ee39", + "metadata": {}, + "outputs": [], "source": [ "anim = centerline([results_ana, results_mbt], legend_names=[\"anatrans\", \"mibitrans\"], animate=True)\n", "plt.show()" - ], - "id": "c4e911cc72a4ee39", - "outputs": [ - { - "data": { - "text/plain": [ - "" - ], - "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - }, - { - "data": { - "text/plain": [ - "" - ], - "text/html": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - } - ], - "execution_count": 32 + ] } ], "metadata": { @@ -365,8 +172,7 @@ "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.6" + "pygments_lexer": "ipython2" } }, "nbformat": 4, diff --git a/examples/example_mibitrans_anatrans_comparison.ipynb b/examples/example_mibitrans_anatrans_comparison.ipynb index ec224d1..36b3fb4 100644 --- a/examples/example_mibitrans_anatrans_comparison.ipynb +++ b/examples/example_mibitrans_anatrans_comparison.ipynb @@ -2,13 +2,10 @@ "cells": [ { "cell_type": "code", + "execution_count": null, "id": "initial_id", - "metadata": { - "ExecuteTime": { - "end_time": "2025-12-16T13:43:27.435567Z", - "start_time": "2025-12-16T13:43:25.761575Z" - } - }, + "metadata": {}, + "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import numpy as np\n", @@ -19,9 +16,7 @@ "from mibitrans.transport.models import Anatrans\n", "from mibitrans.transport.models import Mibitrans\n", "from mibitrans.visualize.animation import animate_1d" - ], - "outputs": [], - "execution_count": 1 + ] }, { "cell_type": "markdown", @@ -40,13 +35,10 @@ }, { "cell_type": "code", + "execution_count": null, "id": "29cec10a2865e2ca", - "metadata": { - "ExecuteTime": { - "end_time": "2025-12-16T13:43:30.359745Z", - "start_time": "2025-12-16T13:43:30.353713Z" - } - }, + "metadata": {}, + "outputs": [], "source": [ "hydro = HydrologicalParameters(\n", " velocity=0.277, # Flow velocity [m/d]\n", @@ -85,53 +77,38 @@ " # Model time discretization step size, in [days]\n", " dt = 25\n", ")" - ], - "outputs": [], - "execution_count": 2 + ] }, { "cell_type": "code", + "execution_count": null, "id": "54e9e7e491c986fe", - "metadata": { - "ExecuteTime": { - "end_time": "2025-12-16T13:43:35.973143Z", - "start_time": "2025-12-16T13:43:32.757383Z" - } - }, + "metadata": {}, + "outputs": [], "source": [ "mbt_object = Mibitrans(hydro, att, source, model)\n", "mbt_results = mbt_object.run()\n", "\n", "ana_object = Anatrans(hydro, att, source, model)\n", "ana_results = ana_object.run()" - ], - "outputs": [], - "execution_count": 3 + ] }, { "cell_type": "code", + "execution_count": null, "id": "e323802bcb322adf", - "metadata": { - "ExecuteTime": { - "end_time": "2025-12-16T13:43:38.143992Z", - "start_time": "2025-12-16T13:43:38.124893Z" - } - }, + "metadata": {}, + "outputs": [], "source": [ "%matplotlib notebook" - ], - "outputs": [], - "execution_count": 4 + ] }, { "cell_type": "code", + "execution_count": null, "id": "b3d53214555ede3a", - "metadata": { - "ExecuteTime": { - "end_time": "2025-12-16T13:43:39.946675Z", - "start_time": "2025-12-16T13:43:39.611526Z" - } - }, + "metadata": {}, + "outputs": [], "source": [ "ani = animate_1d(x_axis_parameter=mbt_results.y,\n", " y_axis_parameter=[mbt_results.cxyt[:,:,25], mbt_results.cxyt[:,:,50],\n", @@ -146,48 +123,14 @@ " linestyle=[\"-\", \"-\", \"-\", \"-\", \"--\", \"--\", \"--\", \"--\"])\n", "plt.xlabel(\"y-position [m]\")\n", "plt.show()\n" - ], - "outputs": [ - { - "data": { - "text/plain": [ - "" - ], - "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - }, - { - "data": { - "text/plain": [ - "" - ], - "text/html": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - } - ], - "execution_count": 5 + ] }, { "cell_type": "code", + "execution_count": null, "id": "78a4f70e30d859ea", - "metadata": { - "ExecuteTime": { - "end_time": "2025-12-16T13:44:04.330475Z", - "start_time": "2025-12-16T13:44:04.299167Z" - } - }, + "metadata": {}, + "outputs": [], "source": [ "ani1 = animate_1d(x_axis_parameter=mbt_results.x,\n", " y_axis_parameter=[mbt_results.cxyt[:,75,:], mbt_results.cxyt[:,84,:],\n", @@ -202,48 +145,14 @@ " linestyle=[\"-\", \"-\", \"-\", \"-\", \"--\", \"--\", \"--\", \"--\"])\n", "plt.xlabel(\"x-position [m]\")\n", "plt.show()\n" - ], - "outputs": [ - { - "data": { - "text/plain": [ - "" - ], - "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - }, - { - "data": { - "text/plain": [ - "" - ], - "text/html": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - } - ], - "execution_count": 6 + ] }, { "cell_type": "code", + "execution_count": null, "id": "77b3c43e8ed23283", - "metadata": { - "ExecuteTime": { - "end_time": "2025-12-16T13:44:39.082231Z", - "start_time": "2025-12-16T13:44:39.039946Z" - } - }, + "metadata": {}, + "outputs": [], "source": [ "plt.figure()\n", "mbt_results.breakthrough(50, 0, color=\"darkgreen\", linestyle=\"-\", label=\"Mibitrans 50m\")\n", @@ -257,38 +166,7 @@ "plt.title(\"Breakthrough plot of Mibitrans and Anatrans model, at various x locations.\")\n", "plt.legend()\n", "plt.show()" - ], - "outputs": [ - { - "data": { - "text/plain": [ - "" - ], - "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - }, - { - "data": { - "text/plain": [ - "" - ], - "text/html": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - } - ], - "execution_count": 7 + ] }, { "cell_type": "markdown", @@ -300,13 +178,10 @@ }, { "cell_type": "code", + "execution_count": null, "id": "ae416dc23dd6b334", - "metadata": { - "ExecuteTime": { - "end_time": "2025-12-16T13:46:57.576523Z", - "start_time": "2025-12-16T13:46:37.590707Z" - } - }, + "metadata": {}, + "outputs": [], "source": [ "parameter_list = [0.1, 0.5, 1, 5, 10]\n", "output_list_mbt = []\n", @@ -326,31 +201,14 @@ " ana_object_alpha.hydrological_parameters.alpha_z = par / 100\n", " ana_res_alp = ana_object_alpha.run()\n", " output_list_ana.append(ana_res_alp.cxyt)" - ], - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "starting run with par = 0.1\n", - "starting run with par = 0.5\n", - "starting run with par = 1\n", - "starting run with par = 5\n", - "starting run with par = 10\n" - ] - } - ], - "execution_count": 10 + ] }, { "cell_type": "code", + "execution_count": null, "id": "93a0274d7116f5f", - "metadata": { - "ExecuteTime": { - "end_time": "2025-12-16T13:47:00.001060Z", - "start_time": "2025-12-16T13:46:59.952945Z" - } - }, + "metadata": {}, + "outputs": [], "source": [ "colors_mbt = [\"darkgreen\", \"limegreen\", \"cornflowerblue\", \"blue\", \"indigo\"]\n", "colors_ana = [\"green\", \"greenyellow\", \"darkturquoise\", \"dodgerblue\", \"darkviolet\"]\n", @@ -361,48 +219,14 @@ " plt.plot(ana_res_alp.x, conc[-1,75,:], color=colors_ana[i], linestyle=\"--\", label=f\"Anatrans a={parameter_list[i]}m\")\n", "plt.legend()\n", "plt.show()" - ], - "outputs": [ - { - "data": { - "text/plain": [ - "" - ], - "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - }, - { - "data": { - "text/plain": [ - "" - ], - "text/html": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - } - ], - "execution_count": 11 + ] }, { "cell_type": "code", + "execution_count": null, "id": "ea1d7ea112addeff", - "metadata": { - "ExecuteTime": { - "end_time": "2025-12-16T13:47:19.803283Z", - "start_time": "2025-12-16T13:47:19.759187Z" - } - }, + "metadata": {}, + "outputs": [], "source": [ "colors_mbt = [\"darkgreen\", \"limegreen\", \"cornflowerblue\", \"blue\", \"indigo\"]\n", "colors_ana = [\"green\", \"greenyellow\", \"darkturquoise\", \"dodgerblue\", \"darkviolet\"]\n", @@ -415,48 +239,14 @@ " linestyle=\"--\", label=f\"Anatrans a={parameter_list[i]}m\")\n", "plt.legend()\n", "plt.show()" - ], - "outputs": [ - { - "data": { - "text/plain": [ - "" - ], - "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - }, - { - "data": { - "text/plain": [ - "" - ], - "text/html": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - } - ], - "execution_count": 12 + ] }, { "cell_type": "code", + "execution_count": null, "id": "2e8640aff2864ca7", - "metadata": { - "ExecuteTime": { - "end_time": "2025-12-16T13:48:19.412475Z", - "start_time": "2025-12-16T13:47:58.834855Z" - } - }, + "metadata": {}, + "outputs": [], "source": [ "parameter_list_v = [0.1, 0.1, 0.1, 0.1, 0.1]\n", "parameter_list_a = [10, 5, 2, 1, 0.5]\n", @@ -481,31 +271,14 @@ " ana_va.hydrological_parameters.alpha_z = 0#para / 100\n", " ana_res_va = ana_va.run()\n", " output_list_ana_v.append(ana_res_va.cxyt)" - ], - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "starting run with parv = 0.1 and para = 10\n", - "starting run with parv = 0.1 and para = 5\n", - "starting run with parv = 0.1 and para = 2\n", - "starting run with parv = 0.1 and para = 1\n", - "starting run with parv = 0.1 and para = 0.5\n" - ] - } - ], - "execution_count": 13 + ] }, { "cell_type": "code", + "execution_count": null, "id": "f5383ae968db9bf6", - "metadata": { - "ExecuteTime": { - "end_time": "2025-12-16T13:48:42.428498Z", - "start_time": "2025-12-16T13:48:42.377594Z" - } - }, + "metadata": {}, + "outputs": [], "source": [ "colors_mbt = [\"darkgreen\", \"limegreen\", \"cornflowerblue\", \"blue\", \"indigo\"]\n", "colors_ana = [\"green\", \"greenyellow\", \"darkturquoise\", \"dodgerblue\", \"darkviolet\"]\n", @@ -519,48 +292,14 @@ "plt.xlim((0,300))\n", "plt.legend()\n", "plt.show()" - ], - "outputs": [ - { - "data": { - "text/plain": [ - "" - ], - "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - }, - { - "data": { - "text/plain": [ - "" - ], - "text/html": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - } - ], - "execution_count": 14 + ] }, { "cell_type": "code", + "execution_count": null, "id": "f38d34e6b66ce99e", - "metadata": { - "ExecuteTime": { - "end_time": "2025-12-16T13:48:55.435247Z", - "start_time": "2025-12-16T13:48:55.404510Z" - } - }, + "metadata": {}, + "outputs": [], "source": [ "colors_mbt = [\"darkgreen\", \"limegreen\", \"cornflowerblue\", \"blue\", \"indigo\"]\n", "colors_ana = [\"green\", \"greenyellow\", \"darkturquoise\", \"dodgerblue\", \"darkviolet\"]\n", @@ -572,48 +311,14 @@ " label=f\"Anatrans a={parameter_list[i]}m\")\n", "plt.legend()\n", "plt.show()" - ], - "outputs": [ - { - "data": { - "text/plain": [ - "" - ], - "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - }, - { - "data": { - "text/plain": [ - "" - ], - "text/html": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - } - ], - "execution_count": 15 + ] }, { "cell_type": "code", + "execution_count": null, "id": "f01c7d5012b64963", - "metadata": { - "ExecuteTime": { - "end_time": "2025-12-16T13:49:04.784533Z", - "start_time": "2025-12-16T13:49:04.723620Z" - } - }, + "metadata": {}, + "outputs": [], "source": [ "mbtv=output_list_mbt_v\n", "anav=output_list_ana_v\n", @@ -631,48 +336,14 @@ "plt.xlabel(\"y-position [m]\")\n", "plt.xlim((0,350))\n", "plt.show()\n" - ], - "outputs": [ - { - "data": { - "text/plain": [ - "" - ], - "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - }, - { - "data": { - "text/plain": [ - "" - ], - "text/html": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - } - ], - "execution_count": 16 + ] }, { "cell_type": "code", + "execution_count": null, "id": "4f70026152c8958e", - "metadata": { - "ExecuteTime": { - "end_time": "2025-12-16T13:49:44.739679Z", - "start_time": "2025-12-16T13:49:44.406865Z" - } - }, + "metadata": {}, + "outputs": [], "source": [ "mbtv=output_list_mbt_v\n", "anav=output_list_ana_v\n", @@ -690,38 +361,7 @@ "plt.xlabel(\"y-position [m]\")\n", "\n", "plt.show()" - ], - "outputs": [ - { - "data": { - "text/plain": [ - "" - ], - "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '%s' message type: \",\n msg_type,\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '%s' message: \", msg_type, msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_%s' callback:\",\n msg_type,\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n buttons: event.buttons,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"
\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n" - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - }, - { - "data": { - "text/plain": [ - "" - ], - "text/html": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - } - ], - "execution_count": 17 + ] } ], "metadata": { From 0663e22bbf403658261a62de79151fe56f0e3a82 Mon Sep 17 00:00:00 2001 From: Bakker Date: Thu, 15 Jan 2026 14:23:00 +0100 Subject: [PATCH 04/19] Added more descriptive legend names --- examples/example_keesler.ipynb | 39 ++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/examples/example_keesler.ipynb b/examples/example_keesler.ipynb index d40ccd3..c3db2dd 100644 --- a/examples/example_keesler.ipynb +++ b/examples/example_keesler.ipynb @@ -48,7 +48,18 @@ " alpha_y = 3.25 / ft, #m\n", " alpha_z = 0\n", ")\n", - "print(hydro_pars.velocity)" + "print(hydro_pars.velocity)\n", + "print(hydro_pars.h_conductivity)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "77f7bd8c2feb2c6c", + "metadata": {}, + "outputs": [], + "source": [ + "0.000057-5" ] }, { @@ -108,6 +119,16 @@ ")" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "6a53c0d880fc1b09", + "metadata": {}, + "outputs": [], + "source": [ + "10/ft" + ] + }, { "cell_type": "markdown", "id": "bcf58154d070a76f", @@ -205,9 +226,9 @@ "outputs": [], "source": [ "time_point = 365/5\n", - "mbt_results.centerline(time=time_point, label=\"mbt\", color=\"blue\")\n", - "ana_results.centerline(time=time_point, label=\"ana\", color=\"red\", linestyle=\"--\")\n", - "bio_results.centerline(time=time_point, label=\"bio\", color=\"green\", linestyle=\":\")\n", + "mbt_results.centerline(time=time_point, label=\"Mibitrans\", color=\"blue\")\n", + "ana_results.centerline(time=time_point, label=\"Anatrans\", color=\"red\", linestyle=\"--\")\n", + "bio_results.centerline(time=time_point, label=\"Bioscreen\", color=\"green\", linestyle=\":\")\n", "plt.xlim((-5,100)) # Reduce the x-axis limit to better observe differences\n", "plt.legend()\n", "plt.show()" @@ -239,7 +260,7 @@ "metadata": {}, "outputs": [], "source": [ - "anim = mbt.centerline([mbt_results, ana_results, bio_results], legend_names=[\"mbt\", \"ana\", \"bio\"], animate=True)\n", + "anim = mbt.centerline([mbt_results, ana_results, bio_results], legend_names=[\"Mibitrans\", \"Anatrans\", \"Bioscreen\"], animate=True)\n", "plt.show()" ] }, @@ -265,7 +286,7 @@ "metadata": {}, "outputs": [], "source": [ - "anim = mbt.centerline([mbt_results_instant, ana_results_instant, bio_results_instant], legend_names=[\"mbt\", \"ana\", \"bio\"], animate=True)\n", + "anim = mbt.centerline([mbt_results_instant, ana_results_instant, bio_results_instant], legend_names=[\"Mibitrans\", \"Anatrans\", \"Bioscreen\"], animate=True)\n", "plt.show()" ] }, @@ -278,9 +299,9 @@ "source": [ "time_point = 365 * 5\n", "plt.figure()\n", - "mbt_results_instant.centerline(time=time_point, label=\"mbt\", color=\"blue\")\n", - "ana_results_instant.centerline(time=time_point, label=\"ana\", color=\"red\", linestyle=\"--\")\n", - "bio_results_instant.centerline(time=time_point, label=\"bio\", color=\"green\", linestyle=\":\")\n", + "mbt_results_instant.centerline(time=time_point, label=\"Mibitrans\", color=\"blue\")\n", + "ana_results_instant.centerline(time=time_point, label=\"Anatrans\", color=\"red\", linestyle=\"--\")\n", + "bio_results_instant.centerline(time=time_point, label=\"Bioscreen\", color=\"green\", linestyle=\":\")\n", "\n", "\n", "field_data_x = np.array([0, 32/ft, 64/ft, 192/ft, 288/ft])\n", From d329e77b0c3394df744df2755e454c088f8b0f49 Mon Sep 17 00:00:00 2001 From: Bakker Date: Mon, 19 Jan 2026 16:12:04 +0100 Subject: [PATCH 05/19] Fixed animations not working in Notebook Changed %matplotlib notebook to %matplotlib ipympl --- examples/example_keesler.ipynb | 78 ++++++++++--------- ...xample_mibitrans_anatrans_comparison.ipynb | 40 ++++++---- examples/example_walkthrough.ipynb | 24 ++++-- 3 files changed, 85 insertions(+), 57 deletions(-) diff --git a/examples/example_keesler.ipynb b/examples/example_keesler.ipynb index c3db2dd..a85cd86 100644 --- a/examples/example_keesler.ipynb +++ b/examples/example_keesler.ipynb @@ -3,7 +3,7 @@ { "cell_type": "code", "execution_count": null, - "id": "63370b00e73a3f77", + "id": "ea64c92477bb5226", "metadata": {}, "outputs": [], "source": [ @@ -34,7 +34,7 @@ { "cell_type": "code", "execution_count": null, - "id": "d03bc12d32df84ac", + "id": "e9bc7c74150eecf0", "metadata": {}, "outputs": [], "source": [ @@ -44,27 +44,17 @@ " h_conductivity = 0.011 / 100 * 3600 * 24, #m/d\n", " h_gradient = 0.003, # m/m\n", " porosity = 0.3,\n", - " alpha_x = 32.5 / ft, #m\n", - " alpha_y = 3.25 / ft, #m\n", + " alpha_x = 13.3 /ft,#32.5 / ft, #m\n", + " alpha_y = 1.3/ft, #3.25 / ft, #m\n", " alpha_z = 0\n", ")\n", "print(hydro_pars.velocity)\n", "print(hydro_pars.h_conductivity)" ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "77f7bd8c2feb2c6c", - "metadata": {}, - "outputs": [], - "source": [ - "0.000057-5" - ] - }, { "cell_type": "markdown", - "id": "bb52ab633659cab4", + "id": "c43a620f2aebcd82", "metadata": {}, "source": [ "The fraction of organic carbon is 0.000057 based of lab analysis, soil bulk density is estimated to be 1.7 (kg/L) [1]. Partition coefficient of BTEX differs by order of magnitude (38, 135, 95, 240 L/kg for B, T, E, X), [1] uses the value of benzene; 38 L/kg. Electron acceptor concentrations are based on groundwater sampling performed in 1995. For oxygen, nitrate and sulfate, the difference between background concentration in the aquifer and minimum concentrations in the plume are used. For ferrous iron and methane, average concentrations in the plume area are used. Values differ somewhat from those reported in [4] (elaborate?)." @@ -73,7 +63,7 @@ { "cell_type": "code", "execution_count": null, - "id": "6688d6a2be9895a8", + "id": "21ace43c3711a552", "metadata": {}, "outputs": [], "source": [ @@ -106,7 +96,7 @@ { "cell_type": "code", "execution_count": null, - "id": "72fbc1542ca07cd0", + "id": "15bd8aad71c1ec1e", "metadata": {}, "outputs": [], "source": [ @@ -119,19 +109,9 @@ ")" ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "6a53c0d880fc1b09", - "metadata": {}, - "outputs": [], - "source": [ - "10/ft" - ] - }, { "cell_type": "markdown", - "id": "bcf58154d070a76f", + "id": "f51ce97cbb15f0e9", "metadata": {}, "source": [ "Model dimensions used for the BIOSCREEN model are; length = 320ft, width = 200ft, duration=6y [1]. In BIOSCREEN resolution is always a fraction of model dimensions, 1/10 for length, 1/4 for width and 1/10 for time. For the mibitrans package, model resolution is freely changeable" @@ -140,7 +120,7 @@ { "cell_type": "code", "execution_count": null, - "id": "b0215d655ce763b5", + "id": "8c657f6368a591bb", "metadata": {}, "outputs": [], "source": [ @@ -221,10 +201,12 @@ { "cell_type": "code", "execution_count": null, - "id": "4d1b4436f0aa185b", + "id": "b315985d03b544d3", "metadata": {}, "outputs": [], "source": [ + "plt.clf() #Clear any previous plots\n", + "\n", "time_point = 365/5\n", "mbt_results.centerline(time=time_point, label=\"Mibitrans\", color=\"blue\")\n", "ana_results.centerline(time=time_point, label=\"Anatrans\", color=\"red\", linestyle=\"--\")\n", @@ -237,17 +219,17 @@ { "cell_type": "code", "execution_count": null, - "id": "ad0e7d335620f2c2", + "id": "631469add3ef8e24", "metadata": {}, "outputs": [], "source": [ "# Needed to show animations in Jupyter Notebook\n", - "%matplotlib notebook" + "%matplotlib ipympl" ] }, { "cell_type": "markdown", - "id": "d145f437f66d2c5d", + "id": "8c2ca032cca0abb4", "metadata": {}, "source": [ "Over time, the source concentration for the Bioscreen model increases to the correct value, and the results resolve to that of the Anatrans model. Differences between the Anatrans and Mibitrans model decrease" @@ -256,7 +238,7 @@ { "cell_type": "code", "execution_count": null, - "id": "b3887af76478a217", + "id": "49cf81bb814503c9", "metadata": {}, "outputs": [], "source": [ @@ -282,7 +264,7 @@ { "cell_type": "code", "execution_count": null, - "id": "e2c45a51f2c472d3", + "id": "deff0aeaac4b0bd3", "metadata": {}, "outputs": [], "source": [ @@ -290,10 +272,19 @@ "plt.show()" ] }, + { + "cell_type": "markdown", + "id": "6a972fba6a3162d", + "metadata": {}, + "source": [ + "## Comparison to field data\n", + "\n" + ] + }, { "cell_type": "code", "execution_count": null, - "id": "6ec97664c7c1f918", + "id": "9920535a966bbfc4", "metadata": {}, "outputs": [], "source": [ @@ -311,9 +302,20 @@ "plt.show()" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "4e58b23852553780", + "metadata": {}, + "outputs": [], + "source": [ + "new_hydrology =\n", + "bio_model." + ] + }, { "cell_type": "markdown", - "id": "eb41bc730334f094", + "id": "9020b5a55a397f08", "metadata": {}, "source": [ "According to Bioscreen, the above model predicts the measured field concentrations fairly well" @@ -322,7 +324,7 @@ { "cell_type": "code", "execution_count": null, - "id": "5ec0210f2f0340ab", + "id": "5ec341476e56cd1e", "metadata": {}, "outputs": [], "source": [ diff --git a/examples/example_mibitrans_anatrans_comparison.ipynb b/examples/example_mibitrans_anatrans_comparison.ipynb index 36b3fb4..8324142 100644 --- a/examples/example_mibitrans_anatrans_comparison.ipynb +++ b/examples/example_mibitrans_anatrans_comparison.ipynb @@ -96,17 +96,17 @@ { "cell_type": "code", "execution_count": null, - "id": "e323802bcb322adf", + "id": "531981a24a7e1d8e", "metadata": {}, "outputs": [], "source": [ - "%matplotlib notebook" + "%matplotlib ipympl" ] }, { "cell_type": "code", "execution_count": null, - "id": "b3d53214555ede3a", + "id": "3eae04b06adb0c90", "metadata": {}, "outputs": [], "source": [ @@ -122,13 +122,15 @@ " \"Anatrans x=50m\", \"Anatrans x=100m\", \"Anatrans x=200m\", \"Anatrans x=400m\"],\n", " linestyle=[\"-\", \"-\", \"-\", \"-\", \"--\", \"--\", \"--\", \"--\"])\n", "plt.xlabel(\"y-position [m]\")\n", + "plt.ylabel(r\"Concentration [$g/m^3$]\")\n", + "ani.save(\"transverse_animation_2.gif\", fps=10)\n", "plt.show()\n" ] }, { "cell_type": "code", "execution_count": null, - "id": "78a4f70e30d859ea", + "id": "544205a3d5565f79", "metadata": {}, "outputs": [], "source": [ @@ -144,13 +146,14 @@ " \"Anatrans y=0m\", \"Anatrans y=9m\", \"Anatrans y=25m\", \"Anatrans y=50m\"],\n", " linestyle=[\"-\", \"-\", \"-\", \"-\", \"--\", \"--\", \"--\", \"--\"])\n", "plt.xlabel(\"x-position [m]\")\n", + "plt.ylabel(r\"Concentration [$g/m^3$]\")\n", "plt.show()\n" ] }, { "cell_type": "code", "execution_count": null, - "id": "77b3c43e8ed23283", + "id": "cbdedb3c17415339", "metadata": {}, "outputs": [], "source": [ @@ -316,7 +319,7 @@ { "cell_type": "code", "execution_count": null, - "id": "f01c7d5012b64963", + "id": "c644d7afc0855414", "metadata": {}, "outputs": [], "source": [ @@ -330,18 +333,23 @@ " time_parameter=mbt_res_va.t,\n", " y_colors=[\"darkgreen\", \"limegreen\", \"cornflowerblue\", \"blue\", \"indigo\",\n", " \"green\", \"greenyellow\", \"darkturquoise\", \"dodgerblue\", \"violet\"],\n", - " y_names=[\"Mibitrans a=0.1\", \"Mibitrans a=0.5\", \"Mibitrans a=1\", \"Mibitrans a=5\", \"Mibitrans a=10\",\n", - " \"Anatrans a=0.1\", \"Anatrans a=0.5\", \"Anatrans a=1\", \"Anatrans a=5\", \"Anatrans a=10\"],\n", + " y_names=[r\"Mibitrans $\\alpha_l=0.1$\", r\"Mibitrans $\\alpha_l=0.5$\", r\"Mibitrans $\\alpha_l=1$\",\n", + " r\"Mibitrans $\\alpha_l=5$\", r\"Mibitrans $\\alpha_l=10$\", r\"Anatrans $\\alpha_l=0.1$\",\n", + " r\"Anatrans $\\alpha_l=0.5$\", r\"Anatrans $\\alpha_l=1$\", r\"Anatrans $\\alpha_l=5$\",\n", + " r\"Anatrans $\\alpha_l=10$\"\n", + " ],\n", " linestyle=[\"-\", \"-\", \"-\", \"-\", \"-\", \"--\", \"--\", \"--\", \"--\", \"--\"])\n", - "plt.xlabel(\"y-position [m]\")\n", + "plt.xlabel(\"x-position [m]\")\n", + "plt.ylabel(r\"Concentration [$g/m^3$]\")\n", "plt.xlim((0,350))\n", - "plt.show()\n" + "plt.show()\n", + "ani.save(\"centerline_animation.gif\", fps=10)\n" ] }, { "cell_type": "code", "execution_count": null, - "id": "4f70026152c8958e", + "id": "6c7bc203fd0dfa00", "metadata": {}, "outputs": [], "source": [ @@ -355,11 +363,15 @@ " time_parameter=mbt_res_va.t,\n", " y_colors=[\"darkgreen\", \"limegreen\", \"cornflowerblue\", \"blue\", \"indigo\",\n", " \"green\", \"greenyellow\", \"darkturquoise\", \"dodgerblue\", \"violet\"],\n", - " y_names=[\"Mibitrans a=0.1\", \"Mibitrans a=0.5\", \"Mibitrans a=1\", \"Mibitrans a=5\", \"Mibitrans a=10\",\n", - " \"Anatrans a=0.1\", \"Anatrans a=0.5\", \"Anatrans a=1\", \"Anatrans a=5\", \"Anatrans a=10\"],\n", + " y_names=[r\"Mibitrans $\\alpha_l=0.1$\", r\"Mibitrans $\\alpha_l=0.5$\", r\"Mibitrans $\\alpha_l=1$\",\n", + " r\"Mibitrans $\\alpha_l=5$\", r\"Mibitrans $\\alpha_l=10$\", r\"Anatrans $\\alpha_l=0.1$\",\n", + " r\"Anatrans $\\alpha_l=0.5$\", r\"Anatrans $\\alpha_l=1$\", r\"Anatrans $\\alpha_l=5$\",\n", + " r\"Anatrans $\\alpha_l=10$\"\n", + " ],\n", " linestyle=[\"-\", \"-\", \"-\", \"-\", \"-\", \"--\", \"--\", \"--\", \"--\", \"--\"])\n", "plt.xlabel(\"y-position [m]\")\n", - "\n", + "plt.ylabel(r\"Concentration [$g/m^3$]\")\n", + "#ani.save(\"transverse_animation.gif\", fps=10)\n", "plt.show()" ] } diff --git a/examples/example_walkthrough.ipynb b/examples/example_walkthrough.ipynb index 940cbaf..a70b14f 100644 --- a/examples/example_walkthrough.ipynb +++ b/examples/example_walkthrough.ipynb @@ -828,7 +828,7 @@ "outputs": [], "source": [ "# Needed to show animations in Jupyter Notebooks\n", - "%matplotlib notebook" + "%matplotlib ipympl" ] }, { @@ -839,12 +839,26 @@ "source": [ "# Output needs to be assigned to variable for animation to work\n", "ani = centerline([mbt_nodecay, mbt_lineardecay, mbt_instant], time=6*365, legend_names=legend, animate=True)\n", - "plt.show()\n", - "\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "# Animation of breakthrough curve instead shows a timelapse of drawing of each curve\n", "ani1 = breakthrough([bio_nodecay, bio_lineardecay, bio_instant], x_position=20, legend_names=legend, animate=True)\n", - "plt.show()\n", - "\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "# For the 3d surface plot, an entirely new plot needs to be generated per time step\n", "# This can cause slightly longer execution times\n", "ani2 = ana_instant.plume_3d(time=6*365, animate=True, cmap=\"viridis\")\n", From a357e4105f65320b89003e4d6038e3c3903d032c Mon Sep 17 00:00:00 2001 From: Bakker Date: Mon, 19 Jan 2026 16:19:05 +0100 Subject: [PATCH 06/19] Added ipympl to dependencies to ensure notebook animations work properly --- examples/example_keesler.ipynb | 4 +++- pyproject.toml | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/example_keesler.ipynb b/examples/example_keesler.ipynb index a85cd86..7516843 100644 --- a/examples/example_keesler.ipynb +++ b/examples/example_keesler.ipynb @@ -309,7 +309,9 @@ "metadata": {}, "outputs": [], "source": [ - "new_hydrology =\n", + "new_hydrology = mbt.HydrologicalParameters(\n", + "\n", + ")\n", "bio_model." ] }, diff --git a/pyproject.toml b/pyproject.toml index d2ef3cf..ea7be5d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ dependencies = [ "cycler", "fonttools", "iniconfig", + "ipympl", "kiwisolver", "matplotlib", "numpy", From 0e4349e7d85fb87e65887ee23c2e5aad355914c7 Mon Sep 17 00:00:00 2001 From: Bakker Date: Tue, 20 Jan 2026 16:07:56 +0100 Subject: [PATCH 07/19] Fixed equations, imports. Furthered examples --- examples/example_equation_formulation.ipynb | 136 +++++++++++++++--- examples/example_kohler.ipynb | 51 +++---- ...xample_mibitrans_anatrans_comparison.ipynb | 50 +++++-- examples/example_walkthrough.ipynb | 102 ++++++------- 4 files changed, 220 insertions(+), 119 deletions(-) diff --git a/examples/example_equation_formulation.ipynb b/examples/example_equation_formulation.ipynb index 1d84ef2..88dd6aa 100644 --- a/examples/example_equation_formulation.ipynb +++ b/examples/example_equation_formulation.ipynb @@ -14,7 +14,9 @@ "from mibitrans.data.parameters import ModelParameters\n", "from mibitrans.data.parameters import SourceParameters\n", "from mibitrans.transport.models import Anatrans\n", - "from mibitrans.visualize.plot_line import centerline" + "from mibitrans.visualize.plot_line import centerline\n", + "from scipy.special import erfcx, erf, erfc\n", + "import mibitrans as mbt" ] }, { @@ -52,16 +54,16 @@ " source_zone_boundary=np.array([30/ft, 35/ft, 40/ft]),\n", " source_zone_concentration=np.array([13.68, 2.508, 0.057]),\n", " depth=10/ft,\n", - " total_mass=50000\n", + " total_mass=\"inf\"\n", ")\n", "\n", "model = ModelParameters(\n", " # Model extent in the longitudinal (x) direction in [m].\n", - " model_length = 320/ft,\n", + " model_length = 1000/ft,\n", " # Model extent in the transverse horizontal (y) direction in [m].\n", " model_width = 100/ft,\n", " # Model duration in [days].\n", - " model_time = 6 * 365,\n", + " model_time = 10 * 365,\n", " # Model grid discretization step size in the longitudinal (x) direction, in [m].\n", " dx = 1/ft,\n", " # Model grid discretization step size in the transverse horizontal (y) direction, in [m].\n", @@ -78,7 +80,7 @@ "metadata": {}, "outputs": [], "source": [ - "class AnatransAlternative(Anatrans):\n", + "class AnatransAlternativeSource(Anatrans):\n", " \"\"\"Mibitrans model class with alternative calculations.\"\"\"\n", "\n", " def __init__(\n", @@ -126,32 +128,33 @@ "outputs": [], "source": [ "ana_object = Anatrans(hydro, att, source, model)\n", - "ana_object.run()\n", + "ana_results = ana_object.run()\n", "\n", - "alt_object = AnatransAlternative(hydro, att, source, model)\n", - "alt_object.run()" + "alt_object = AnatransAlternativeSource(hydro, att, source, model)\n", + "alt_results = alt_object.run()" ] }, { "cell_type": "code", "execution_count": null, - "id": "7f6401bd8fb02806", + "id": "8f3caf71b2e44f2e", "metadata": {}, "outputs": [], "source": [ - "ana_object.centerline(color=\"green\")\n", - "alt_object.centerline(color=\"red\", linestyle=\"--\")\n", - "plt.show()" + "%matplotlib ipympl" ] }, { "cell_type": "code", "execution_count": null, - "id": "d63c6e5dba784862", + "id": "7f6401bd8fb02806", "metadata": {}, "outputs": [], "source": [ - "%matplotlib notebook" + "plt.clf()\n", + "ana_results.centerline(color=\"green\")\n", + "alt_results.centerline(color=\"red\", linestyle=\"--\")\n", + "plt.show()" ] }, { @@ -161,7 +164,8 @@ "metadata": {}, "outputs": [], "source": [ - "anim = centerline(model=[ana_object, alt_object],\n", + "plt.clf()\n", + "anim = centerline(model=[ana_results, alt_results],\n", " legend_names=[\"Anatrans model\", \"Alternative model\"],\n", " linestyle=\":\",\n", " animate=True)\n", @@ -175,7 +179,107 @@ "metadata": {}, "outputs": [], "source": [ - "ana_object.plume_3d()\n", + "class AnatransAlternativeDispersion(Anatrans):\n", + " \"\"\"Mibitrans model class with alternative calculations.\"\"\"\n", + "\n", + " def __init__(\n", + " self,\n", + " hydrological_parameters,\n", + " attenuation_parameters,\n", + " source_parameters,\n", + " model_parameters,\n", + " verbose=False,\n", + " ):\n", + " \"\"\"Initialize model class.\"\"\"\n", + " super().__init__(hydrological_parameters, attenuation_parameters, source_parameters, model_parameters, verbose)\n", + "\n", + "\n", + " def _equation_term_z(self, xxx, ttt):\n", + " inner_term = self._src_pars.depth / (2 * np.sqrt(self._hyd_pars.alpha_z * self.rv * ttt))\n", + " return erf(inner_term) - erf(-inner_term)\n", + "\n", + " def _equation_term_y(self, i, xxx, yyy, ttt):\n", + " div_term = 2 * np.sqrt(self._hyd_pars.alpha_y * self.rv * ttt)\n", + " term = erf((yyy + self.y_source[i]) / div_term) - erf((yyy - self.y_source[i]) / div_term)\n", + " term[np.isnan(term)] = 0\n", + " return term\n", + "\n", + " def _calculate_concentration_for_all_xyt(self, xxx, yyy, ttt):\n", + " cxyt = 0\n", + " #adapted decay_sqrt\n", + " decay_sqrt = np.sqrt(1 + 4 * (self._decay_rate - self.k_source) * self._hyd_pars.alpha_x / self.rv)\n", + " x_term = self._equation_term_x(xxx, ttt, decay_sqrt)\n", + " additional_x = self._equation_term_additional_x(xxx, ttt, decay_sqrt)\n", + " z_term = self._equation_term_z(xxx, ttt)\n", + " source_decay = self._equation_term_source_decay(xxx, ttt)\n", + " for i in range(len(self.c_source)):\n", + " y_term = self._equation_term_y(i, xxx, yyy,ttt)\n", + " cxyt_step = 1 / 8 * self.c_source[i] * source_decay * (x_term + additional_x) * y_term * z_term\n", + " cxyt += cxyt_step\n", + " if self._mode == \"instant_reaction\":\n", + " self.cxyt_noBC = cxyt.copy()\n", + " cxyt -= self.biodegradation_capacity\n", + " cxyt = np.where(cxyt < 0, 0, cxyt)\n", + " self.has_run = True\n", + " return cxyt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "79ba321701becd93", + "metadata": {}, + "outputs": [], + "source": [ + "plt.clf()\n", + "alt_disp_object = AnatransAlternativeDispersion(hydro, att, source, model)\n", + "alt_disp_results = alt_disp_object.run()\n", + "ana_results.centerline(color=\"green\")\n", + "alt_results.centerline(color=\"red\", linestyle=\"--\")\n", + "alt_disp_results.centerline(color=\"blue\", linestyle=\"-.\")\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c2ef5cb8f80d1879", + "metadata": {}, + "outputs": [], + "source": [ + "plt.clf()\n", + "anim = centerline(model=[ana_results, alt_results, alt_disp_results],\n", + " legend_names=[\"Anatrans model\", \"Alternative model\", \"Alt disp model\"],\n", + " linestyle=\":\",\n", + " animate=True)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3df1125ff510edea", + "metadata": {}, + "outputs": [], + "source": [ + "plt.clf()\n", + "anim = mbt.plume_3d(model=alt_disp_results,\n", + " animate=True,\n", + " cmap='viridis')\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "355d03639545ee93", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "anim = ana_results.plume_3d(\n", + " animate=True,\n", + " cmap='viridis')\n", "plt.show()" ] } diff --git a/examples/example_kohler.ipynb b/examples/example_kohler.ipynb index f465c5e..b7dee18 100644 --- a/examples/example_kohler.ipynb +++ b/examples/example_kohler.ipynb @@ -9,16 +9,7 @@ "source": [ "import matplotlib.pyplot as plt\n", "import numpy as np\n", - "\n", - "from mibitrans import ElectronAcceptors\n", - "from mibitrans import UtilizationFactor\n", - "from mibitrans.data.parameters import AttenuationParameters\n", - "from mibitrans.data.parameters import HydrologicalParameters\n", - "from mibitrans.data.parameters import ModelParameters\n", - "from mibitrans.data.parameters import SourceParameters\n", - "from mibitrans.transport.models import Anatrans\n", - "from mibitrans.transport.models import Mibitrans\n", - "from mibitrans.visualize.plot_line import centerline" + "import mibitrans as mbt" ] }, { @@ -36,28 +27,28 @@ "metadata": {}, "outputs": [], "source": [ - "hydro = HydrologicalParameters(\n", - " velocity=0.1,\n", + "hydro = mbt.HydrologicalParameters(\n", + " velocity=20.8/3.281/365,\n", " porosity=0.25,\n", - " alpha_x=2,\n", - " alpha_y=0.05,\n", + " alpha_x=21.6/3.281,\n", + " alpha_y=21.6/3.281/10,\n", " alpha_z=0\n", ")\n", - "att = AttenuationParameters(\n", - " decay_rate=0,\n", + "att = mbt.AttenuationParameters(\n", + " decay_rate=0,#0.0034,\n", " retardation=1\n", ")\n", - "source = SourceParameters(\n", - " source_zone_boundary=np.array([1, 1.5, 2]),\n", - " source_zone_concentration=np.array([10, 5, 1]),\n", + "source = mbt.SourceParameters(\n", + " source_zone_boundary=np.array([5]),\n", + " source_zone_concentration=np.array([26.125]),\n", " total_mass=\"inf\",\n", - " depth=1,\n", + " depth=50/3.281,\n", ")\n", - "model = ModelParameters(\n", - " model_length=400,\n", - " model_width=10,\n", + "model = mbt.ModelParameters(\n", + " model_length=100,\n", + " model_width=20,\n", " model_time=20*365,\n", - " dx=2,\n", + " dx=0.5,\n", " dy=0.1,\n", " dt=365 / 5,\n", ")" @@ -70,8 +61,8 @@ "metadata": {}, "outputs": [], "source": [ - "ana_obj = Anatrans(hydro, att, source, model)\n", - "ana_obj.instant_reaction(ElectronAcceptors(8, 0, 0, 0, 0), UtilizationFactor(3.5,1,1,1,1))\n", + "ana_obj = mbt.Anatrans(hydro, att, source, model)\n", + "#ana_obj.instant_reaction(mbt.ElectronAcceptors(8.93-0.15, 5.57, 45.3, 76.7, 14.63))\n", "results_ana = ana_obj.run()" ] }, @@ -83,7 +74,7 @@ "outputs": [], "source": [ "# Needed to show animations in Jupyter Notebooks\n", - "%matplotlib notebook" + "%matplotlib ipympl" ] }, { @@ -115,8 +106,8 @@ "metadata": {}, "outputs": [], "source": [ - "mbt_obj = Mibitrans(hydro, att, source, model)\n", - "mbt_obj.instant_reaction(ElectronAcceptors(8, 0, 0, 0, 0), UtilizationFactor(3.5,1,1,1,1))\n", + "mbt_obj = mbt.Mibitrans(hydro, att, source, model)\n", + "#mbt_obj.instant_reaction(mbt.ElectronAcceptors(8.93-0.15, 5.57, 45.3, 76.7, 14.63))\n", "results_mbt = mbt_obj.run()" ] }, @@ -152,7 +143,7 @@ "metadata": {}, "outputs": [], "source": [ - "anim = centerline([results_ana, results_mbt], legend_names=[\"anatrans\", \"mibitrans\"], animate=True)\n", + "anim = mbt.centerline([results_ana, results_mbt], legend_names=[\"anatrans\", \"mibitrans\"], animate=True)\n", "plt.show()" ] } diff --git a/examples/example_mibitrans_anatrans_comparison.ipynb b/examples/example_mibitrans_anatrans_comparison.ipynb index 8324142..f7862f7 100644 --- a/examples/example_mibitrans_anatrans_comparison.ipynb +++ b/examples/example_mibitrans_anatrans_comparison.ipynb @@ -23,14 +23,42 @@ "id": "d24df86e0eb6973d", "metadata": {}, "source": [ - "### Exact solution versus untruncated solution\n", + "# Exact solution versus untruncated solution\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "7e66c813a939d441", + "metadata": {}, + "source": [ + "## Truncated domenico solution\n", + "\n", + "$$\n", + "\\begin{align}\\tag{2}\n", + " C(x, y, z, t) &= \\frac{C_{0}}{8} \\exp \\left[ \\frac{x\\left(1-\\sqrt{1+4\\lambda \\alpha_x/v}\\right)}{2\\alpha_x}\\right] \\\\\n", + " &\\quad \\cdot \\operatorname{erfc} \\left[ \\frac{x - vt\\sqrt{1+4\\lambda \\alpha_x/v}}{2\\sqrt{\\alpha_x vt }} \\right] \\\\\n", + " &\\quad \\cdot \\left\\{ \\operatorname{erf} \\left[ \\frac{y + Y/2}{2\\sqrt{\\alpha_y x}} \\right] - \\operatorname{erf} \\left[ \\frac{y - Y/2}{2\\sqrt{\\alpha_y x)}} \\right] \\right\\} \\\\\n", + " &\\quad \\cdot \\Biggl. \\left\\{ \\operatorname{erf} \\left[ \\frac{z +Z/2}{2\\sqrt{\\alpha_z x)}} \\right] - \\operatorname{erf} \\left[ \\frac{z-Z/2}{2\\sqrt{\\alpha_z x}} \\right] \\right\\} \\nonumber\n", + "\\end{align}\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "b773e32c9f5886da", + "metadata": {}, + "source": [ + "## Exact solution\n", "\n", - "\\begin{equation}\n", - "C(x,y,z,t) = \\sum_{i=1}^{n}\\left(C^*_{0,i}\\frac{x}{8\\sqrt{\\pi D^{'}_{x}}}\\exp(-\\gamma t)\n", - "\\cdot \\int_{0}^{t}\\left[\\frac{1}{\\tau^{\\frac{3}{2}}} \\exp\\left((\\gamma - \\lambda_{EFF})\\tau - \\frac{(x-v^{'}\\tau)^2}{4D^{'}_{x}\\tau}\\right)\n", - "\\cdot \\left\\{ERFC\\left(\\frac{y-Y_i}{2 \\sqrt{D^{'}_{y}\\tau}}\\right)-ERFC\\left(\\frac{y+Y_i}{2 \\sqrt{D^{'}_{y}\\tau}}\\right) \\right\\}\n", - "\\cdot \\left\\{ERFC\\left(\\frac{z-Z}{2 \\sqrt{D^{'}_{z}\\tau}}\\right)-ERFC\\left(\\frac{z+Z}{2 \\sqrt{D^{'}_{z}\\tau}}\\right) \\right\\}\\right] d\\tau \\right)\n", - "\\end{equation}" + "$$\n", + "\\begin{align}\\tag{1}\n", + " C(x,y,z,t) &= \\sum_{i=1}^{n}\\left(C^*_{0,i}\\frac{x}{8\\sqrt{\\pi D^{'}_{x}}}\\exp(-\\gamma t) \\right. \\\\\n", + " &\\quad \\cdot \\int_{0}^{t}\\left[\\frac{1}{\\tau^{\\frac{3}{2}}} \\exp\\left((\\gamma - \\lambda_{eff})\\tau - \\frac{(x-v^{'}\\tau)^2}{4D^{'}_{x}\\tau}\\right) \\right. \\\\\n", + " &\\quad \\cdot \\left\\{\\operatorname{erfc}\\left(\\frac{y-Y_i}{2 \\sqrt{D^{'}_{y}\\tau}}\\right)-\\operatorname{erfc}\\left(\\frac{y+Y_i}{2 \\sqrt{D^{'}_{y}\\tau}}\\right) \\right\\} \\\\\n", + " &\\quad \\left. \\left. \\cdot \\left\\{\\operatorname{erfc}\\left(\\frac{z-Z}{2 \\sqrt{D^{'}_{z}\\tau}}\\right)-\\operatorname{erfc}\\left(\\frac{z+Z}{2 \\sqrt{D^{'}_{z}\\tau}}\\right) \\right\\}\\right] d\\tau \\right)\n", + "\\end{align}\n", + "$$" ] }, { @@ -42,10 +70,10 @@ "source": [ "hydro = HydrologicalParameters(\n", " velocity=0.277, # Flow velocity [m/d]\n", - " porosity=0.25, # Effective soil porosity [-]\n", - " alpha_x=10, # Longitudinal dispersivity, in [m]\n", - " alpha_y=1, # Transverse horizontal dispersivity, in [m]\n", - " alpha_z=0.1 # Transverse vertical dispersivity, in [m]\n", + " porosity=0.25, # Effective soil porosity [-]\n", + " alpha_x=10, # Longitudinal dispersivity, in [m]\n", + " alpha_y=1, # Transverse horizontal dispersivity, in [m]\n", + " alpha_z=0.1 # Transverse vertical dispersivity, in [m]\n", ")\n", "\n", "att = AttenuationParameters(\n", diff --git a/examples/example_walkthrough.ipynb b/examples/example_walkthrough.ipynb index a70b14f..2ed9933 100644 --- a/examples/example_walkthrough.ipynb +++ b/examples/example_walkthrough.ipynb @@ -8,20 +8,7 @@ "source": [ "import matplotlib.pyplot as plt\n", "import numpy as np\n", - "from mibitrans.data.parameter_information import ElectronAcceptors\n", - "from mibitrans.data.parameter_information import UtilizationFactor\n", - "from mibitrans.data.parameters import AttenuationParameters\n", - "from mibitrans.data.parameters import HydrologicalParameters\n", - "from mibitrans.data.parameters import ModelParameters\n", - "from mibitrans.data.parameters import SourceParameters\n", - "from mibitrans.transport.models import Anatrans\n", - "from mibitrans.transport.models import Bioscreen\n", - "from mibitrans.transport.models import Mibitrans\n", - "from mibitrans.visualize.plot_line import breakthrough\n", - "from mibitrans.visualize.plot_line import centerline\n", - "from mibitrans.visualize.plot_line import transverse\n", - "from mibitrans.visualize.plot_surface import plume_2d\n", - "from mibitrans.visualize.plot_surface import plume_3d" + "import mibitrans as mbt" ] }, { @@ -69,7 +56,7 @@ "metadata": {}, "outputs": [], "source": [ - "hydro = HydrologicalParameters(\n", + "hydro = mbt.HydrologicalParameters(\n", " velocity=113.8/ft/365, # Groundwater flow velocity, in [m/day]\n", " porosity=0.25, # Effective soil porosity [-]\n", " alpha_x=13.3/ft, # Longitudinal dispersivity, in [m]\n", @@ -78,7 +65,7 @@ ")\n", "\n", "# Alternative by specifying hydraulic gradient and hydraulic conductivity\n", - "hydro = HydrologicalParameters(\n", + "hydro = mbt.HydrologicalParameters(\n", " h_gradient=0.048, # Hydraulic gradient [-]\n", " h_conductivity=0.495, # Hydraulic conductivity [m/day]\n", " porosity=0.25, # Effective soil porosity [-]\n", @@ -114,13 +101,13 @@ "metadata": {}, "outputs": [], "source": [ - "att = AttenuationParameters(\n", + "att = mbt.AttenuationParameters(\n", " # Retardation factor for transported contaminants\n", " retardation=1\n", ")\n", "\n", "# Alternatively, calculate the retardation factor by supplying soil and contaminant properties\n", - "att = AttenuationParameters(\n", + "att = mbt.AttenuationParameters(\n", " # Soil bulk density in [g/m^3]\n", " bulk_density=1.7,\n", " # Partition coefficient of the transported contaminant to soil organic matter, in [m^3/g]\n", @@ -135,7 +122,7 @@ "# And then check the value:\n", "print(\"The calculated retardation value is: \", att.retardation)\n", "\n", - "att = AttenuationParameters(\n", + "att = mbt.AttenuationParameters(\n", " # Retardation factor for transported contaminants\n", " retardation=1,\n", " # Molecular diffusion, in [m2/day]\n", @@ -158,13 +145,13 @@ "metadata": {}, "outputs": [], "source": [ - "att = AttenuationParameters(\n", + "att = mbt.AttenuationParameters(\n", " # Contaminant first order decay rate in [1/days]\n", " decay_rate=0.0127\n", ")\n", "\n", "# Alternatively, specify the contaminant half life\n", - "att = AttenuationParameters(\n", + "att = mbt.AttenuationParameters(\n", " # Contaminant half life, in [days]\n", " half_life=54.75,\n", ")\n" @@ -183,7 +170,7 @@ "metadata": {}, "outputs": [], "source": [ - "att = AttenuationParameters(\n", + "att = mbt.AttenuationParameters(\n", " # Soil bulk density in [g/m^3]\n", " bulk_density=1.7,\n", " # Partition coefficient of the transported contaminant to soil organic matter, in [m^3/g]\n", @@ -215,7 +202,7 @@ "outputs": [], "source": [ "# Input for a simple source zone with a width of 19.8m (65ft) and a continuous input (infinite source mass)\n", - "source = SourceParameters(\n", + "source = mbt.SourceParameters(\n", " # Source zone boundaries, in [m] (simply using a float instead of a numpy array for\n", " # single source zone input will work as well)\n", " source_zone_boundary=np.array([65/ft]),\n", @@ -228,7 +215,7 @@ ")\n", "\n", "# Alternatively, specify a source mass to allow for source decay\n", - "source = SourceParameters(\n", + "source = mbt.SourceParameters(\n", " source_zone_boundary=np.array([7/ft, 37/ft, 65/ft]),\n", " source_zone_concentration=np.array([13.68, 2.508, 0.057]),\n", " depth=10/ft,\n", @@ -255,7 +242,7 @@ "metadata": {}, "outputs": [], "source": [ - "model = ModelParameters(\n", + "model = mbt.ModelParameters(\n", " # Model extent in the longitudinal (x) direction in [m].\n", " model_length = 320/ft,\n", " # Model extent in the transverse horizontal (y) direction in [m].\n", @@ -288,7 +275,7 @@ "source": [ "# If not all required parameters are specified, an error will be shown;\n", "# HydrologicalParameters needs the porosity, longitudinal dispersivity and transverse horizontal dispersivity as well.\n", - "fake_hydro = HydrologicalParameters(velocity = 1)" + "fake_hydro = mbt.HydrologicalParameters(velocity = 1)" ] }, { @@ -299,7 +286,7 @@ "source": [ "# If the input datatype is not what is expected; source_zone_concentration expects a numpy array\n", "# of the same length as the array given in source_zone_boundary.\n", - "fake_source = SourceParameters(\n", + "fake_source = mbt.SourceParameters(\n", " source_zone_boundary = np.array([1,2,3]),\n", " source_zone_concentration = \"this is a string, not an array\",\n", " depth = 10,\n", @@ -314,7 +301,7 @@ "outputs": [], "source": [ "# Same goes for if the input parameter has a value outside its valid domain; retardation should have a value >= 1\n", - "fake_att = AttenuationParameters(\n", + "fake_att = mbt.AttenuationParameters(\n", " retardation = 0.1\n", ")" ] @@ -348,7 +335,7 @@ "outputs": [], "source": [ "# Initializing the model object\n", - "mbt_object = Mibitrans(\n", + "mbt_object = mbt.Mibitrans(\n", " hydrological_parameters=hydro,\n", " attenuation_parameters=att,\n", " source_parameters=source,\n", @@ -364,15 +351,6 @@ "mbt_results = mbt_object.run()\n" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "?Mibitrans" - ] - }, { "cell_type": "code", "execution_count": null, @@ -405,7 +383,7 @@ "outputs": [], "source": [ "# For streamlined input, use the ElectronAcceptor dataclass;\n", - "ea = ElectronAcceptors(\n", + "ea = mbt.ElectronAcceptors(\n", " # Difference between background oxygen and current oxygen concentration in groundwater, in [g/m^3]\n", " delta_oxygen=1.65,\n", " # Difference between background nitrate and current nitrate concentration in groundwater, in [g/m^3]\n", @@ -421,7 +399,7 @@ "mbt_object.instant_reaction(electron_acceptors=ea)\n", "\n", "# Can adapt utilization factors if needed\n", - "uf = UtilizationFactor(\n", + "uf = mbt.UtilizationFactor(\n", " # utilization factor of oxygen, as mass of oxygen consumed per mass of biodegraded contaminant [g/g].\n", " util_oxygen=2,\n", " # utilization factor of nitrate, as mass of nitrate consumed per mass of biodegraded contaminant [g/g].\n", @@ -515,7 +493,7 @@ "source": [ "# Using the verbose flag, integration steps are printed to console.\n", "# Usefull for longer runs to track progress\n", - "mbt_object = Mibitrans(\n", + "mbt_object = mbt.Mibitrans(\n", " hydrological_parameters=hydro,\n", " attenuation_parameters=att,\n", " source_parameters=source,\n", @@ -543,7 +521,7 @@ "metadata": {}, "outputs": [], "source": [ - "ana_object = Anatrans(\n", + "ana_object = mbt.Anatrans(\n", " hydrological_parameters=hydro,\n", " attenuation_parameters=att,\n", " source_parameters=source,\n", @@ -570,7 +548,7 @@ "metadata": {}, "outputs": [], "source": [ - "bio_object = Bioscreen(\n", + "bio_object = mbt.Bioscreen(\n", " hydrological_parameters=hydro,\n", " attenuation_parameters=att,\n", " source_parameters=source,\n", @@ -595,7 +573,7 @@ "outputs": [], "source": [ "# Will not work; hydrological_parameters should be a HydrologicalParameters class object.\n", - "fake_mbt = Mibitrans(\n", + "fake_mbt = mbt.Mibitrans(\n", " hydrological_parameters=model,\n", " attenuation_parameters=att,\n", " source_parameters=source,\n", @@ -621,7 +599,7 @@ "# Lets make some different model objects to use in the plotting, takes a couple of seconds to run\n", "\n", "#Mibitrans model\n", - "mbt_object = Mibitrans(hydro, att, source, model)\n", + "mbt_object = mbt.Mibitrans(hydro, att, source, model)\n", "mbt_lineardecay = mbt_object.run()\n", "\n", "mbt_object.attenuation_parameters.decay_rate = 0\n", @@ -631,7 +609,7 @@ "mbt_instant = mbt_object.run()\n", "\n", "#Anatrans model\n", - "ana_object = Anatrans(hydro, att, source, model)\n", + "ana_object = mbt.Anatrans(hydro, att, source, model)\n", "ana_lineardecay = ana_object.run()\n", "\n", "ana_object.attenuation_parameters.decay_rate = 0\n", @@ -641,7 +619,7 @@ "ana_instant = ana_object.run()\n", "\n", "#Bioscreen model\n", - "bio_object = Bioscreen(hydro, att, source, model)\n", + "bio_object = mbt.Bioscreen(hydro, att, source, model)\n", "bio_lineardecay = bio_object.run()\n", "\n", "bio_object.attenuation_parameters.decay_rate = 0\n", @@ -666,17 +644,17 @@ "outputs": [], "source": [ "# Pass model object to plotting function, by default, the last time step is used.\n", - "centerline(mbt_nodecay)\n", + "mbt.centerline(mbt_nodecay)\n", "plt.show()\n", "\n", "# If you want to plot somewhere and sometime specific, use the time and y_position arguments.\n", "# It gives the concentration profile at the step closest to what you specified.\n", - "centerline(mbt_nodecay, time=3*365, y_position=5)\n", + "mbt.centerline(mbt_nodecay, time=3*365, y_position=5)\n", "#plt.title(\"No degradation Domenico model 5m away from center, t=6years\")\n", "plt.show()\n", "\n", "# If you want you can change the plot settings to the ones you prefer\n", - "centerline(mbt_nodecay, time=6*365)\n", + "mbt.centerline(mbt_nodecay, time=6*365)\n", "plt.title(\"Better title than the one generated automatically\")\n", "plt.xlabel(\"I have changed!\")\n", "plt.ylabel(\"And so have I\")\n", @@ -686,14 +664,14 @@ "\n", "# Instead of a single model, all line visualization functions accept a list of models to be displayed\n", "# together in a single plot.\n", - "centerline([mbt_nodecay, mbt_lineardecay, mbt_instant], time=6*365, legend_names=legend)\n", + "mbt.centerline([mbt_nodecay, mbt_lineardecay, mbt_instant], time=6*365, legend_names=legend)\n", "plt.title(\"Mibitrans model at plume center for different models, t=6years\")\n", "plt.show()\n", "\n", "# Keyword arguments for plt.plot can be passed on through the function\n", "# Calling function separately per model gives more control over plot layout\n", - "centerline(mbt_nodecay, time=6*365, linestyle=\"--\", color=\"green\", label=legend[0])\n", - "centerline(mbt_lineardecay, time=6*365, linestyle=\"-.\", color=\"red\", label=legend[1])\n", + "mbt.centerline(mbt_nodecay, time=6*365, linestyle=\"--\", color=\"green\", label=legend[0])\n", + "mbt.centerline(mbt_lineardecay, time=6*365, linestyle=\"-.\", color=\"red\", label=legend[1])\n", "plt.title(\"No and linear degradation Mibitrans model at plume center, t=6years\")\n", "plt.legend()\n", "plt.show()\n", @@ -721,8 +699,8 @@ "outputs": [], "source": [ "# Concentration distribution can also be plotted in the transverse direction\n", - "transverse(ana_nodecay, x_position=80, time=6*365, linestyle=\"--\", color=\"green\", label=\"no degradation\")\n", - "transverse(ana_instant, x_position=80, time=6*365, linestyle=\"-.\", color=\"red\", label=\"instant reaction\")\n", + "mbt.transverse(ana_nodecay, x_position=80, time=6*365, linestyle=\"--\", color=\"green\", label=\"no degradation\")\n", + "mbt.transverse(ana_instant, x_position=80, time=6*365, linestyle=\"-.\", color=\"red\", label=\"instant reaction\")\n", "\n", "plt.title(\"No degradation and instant reaction Domenico model at x=80m, t=6years\")\n", "plt.legend()\n", @@ -747,7 +725,7 @@ "outputs": [], "source": [ "# Concentration distribution can also be plotted in the transverse direction\n", - "breakthrough([bio_nodecay, bio_lineardecay, bio_instant], x_position=80,\n", + "mbt.breakthrough([bio_nodecay, bio_lineardecay, bio_instant], x_position=80,\n", " legend_names=[\"no degradation\", \"linear decay\", \"instant reaction\"])\n", "plt.title(\"Breakthrough curve of Bioscreen model for different degradation settings, at x=80m, t=6years\")\n", "plt.show()\n" @@ -767,12 +745,12 @@ "outputs": [], "source": [ "# Plot the x and y concentration distribution for the Mibitrans model, uses plt.pcolormesh\n", - "plume_2d(mbt_nodecay, time=6*365)\n", + "mbt.plume_2d(mbt_nodecay, time=6*365)\n", "plt.title(\"Contaminant plume with no degradation Mibitrans model, t = 6 years\")\n", "plt.show()\n", "\n", "# Function passes plt.colormesh keyword arguments\n", - "plume_2d(mbt_lineardecay, time=6*365, cmap=\"magma\")\n", + "mbt.plume_2d(mbt_lineardecay, time=6*365, cmap=\"magma\")\n", "plt.title(\"Contaminant plume with linear degradation Domenico model, t = 6 years\")\n", "plt.show()\n", "\n", @@ -795,12 +773,12 @@ "outputs": [], "source": [ "# Plot the x and y concentration distribution for no degradation decay model, uses plot_surface\n", - "plume_3d(ana_nodecay, time=6*365)\n", + "mbt.plume_3d(ana_nodecay, time=6*365)\n", "plt.title(\"Contaminant plume with no degradation Anatrans model, t = 6 years\")\n", "plt.show()\n", "\n", "# Function passes plot_surface keyword arguments\n", - "plume_3d(ana_lineardecay, time=6*365, cmap=\"viridis\")\n", + "mbt.plume_3d(ana_lineardecay, time=6*365, cmap=\"viridis\")\n", "plt.title(\"Contaminant plume with linear degradation Anatrans model, t = 6 years\")\n", "plt.show()\n", "\n", @@ -838,7 +816,7 @@ "outputs": [], "source": [ "# Output needs to be assigned to variable for animation to work\n", - "ani = centerline([mbt_nodecay, mbt_lineardecay, mbt_instant], time=6*365, legend_names=legend, animate=True)\n", + "ani = mbt.centerline([mbt_nodecay, mbt_lineardecay, mbt_instant], time=6*365, legend_names=legend, animate=True)\n", "plt.show()" ] }, @@ -849,7 +827,7 @@ "outputs": [], "source": [ "# Animation of breakthrough curve instead shows a timelapse of drawing of each curve\n", - "ani1 = breakthrough([bio_nodecay, bio_lineardecay, bio_instant], x_position=20, legend_names=legend, animate=True)\n", + "ani1 = mbt.breakthrough([bio_nodecay, bio_lineardecay, bio_instant], x_position=20, legend_names=legend, animate=True)\n", "plt.show()" ] }, From c170d3d762a5c393718727b2bf76cbf456338e8b Mon Sep 17 00:00:00 2001 From: Bakker Date: Tue, 27 Jan 2026 11:45:12 +0100 Subject: [PATCH 08/19] Change imports and further work examples --- examples/example_equation_formulation.ipynb | 16 ++-- examples/example_exact_equation.ipynb | 40 ++++----- examples/example_keesler.ipynb | 84 ++++++++++++++++--- ...xample_mibitrans_anatrans_comparison.ipynb | 43 +++++----- examples/example_walkthrough.ipynb | 34 ++++++-- 5 files changed, 146 insertions(+), 71 deletions(-) diff --git a/examples/example_equation_formulation.ipynb b/examples/example_equation_formulation.ipynb index 88dd6aa..4198275 100644 --- a/examples/example_equation_formulation.ipynb +++ b/examples/example_equation_formulation.ipynb @@ -61,15 +61,15 @@ " # Model extent in the longitudinal (x) direction in [m].\n", " model_length = 1000/ft,\n", " # Model extent in the transverse horizontal (y) direction in [m].\n", - " model_width = 100/ft,\n", + " model_width = 300/ft,\n", " # Model duration in [days].\n", " model_time = 10 * 365,\n", " # Model grid discretization step size in the longitudinal (x) direction, in [m].\n", - " dx = 1/ft,\n", + " dx = 2/ft,\n", " # Model grid discretization step size in the transverse horizontal (y) direction, in [m].\n", " dy = 1/ft,\n", " # Model time discretization step size, in [days]\n", - " dt = 365 / 20\n", + " dt = 25\n", ")" ] }, @@ -193,12 +193,11 @@ " \"\"\"Initialize model class.\"\"\"\n", " super().__init__(hydrological_parameters, attenuation_parameters, source_parameters, model_parameters, verbose)\n", "\n", - "\n", - " def _equation_term_z(self, xxx, ttt):\n", + " def _equation_term_z(self, ttt):\n", " inner_term = self._src_pars.depth / (2 * np.sqrt(self._hyd_pars.alpha_z * self.rv * ttt))\n", " return erf(inner_term) - erf(-inner_term)\n", "\n", - " def _equation_term_y(self, i, xxx, yyy, ttt):\n", + " def _equation_term_y(self, i, yyy, ttt):\n", " div_term = 2 * np.sqrt(self._hyd_pars.alpha_y * self.rv * ttt)\n", " term = erf((yyy + self.y_source[i]) / div_term) - erf((yyy - self.y_source[i]) / div_term)\n", " term[np.isnan(term)] = 0\n", @@ -210,10 +209,10 @@ " decay_sqrt = np.sqrt(1 + 4 * (self._decay_rate - self.k_source) * self._hyd_pars.alpha_x / self.rv)\n", " x_term = self._equation_term_x(xxx, ttt, decay_sqrt)\n", " additional_x = self._equation_term_additional_x(xxx, ttt, decay_sqrt)\n", - " z_term = self._equation_term_z(xxx, ttt)\n", + " z_term = self._equation_term_z(ttt)\n", " source_decay = self._equation_term_source_decay(xxx, ttt)\n", " for i in range(len(self.c_source)):\n", - " y_term = self._equation_term_y(i, xxx, yyy,ttt)\n", + " y_term = self._equation_term_y(i, yyy,ttt)\n", " cxyt_step = 1 / 8 * self.c_source[i] * source_decay * (x_term + additional_x) * y_term * z_term\n", " cxyt += cxyt_step\n", " if self._mode == \"instant_reaction\":\n", @@ -276,7 +275,6 @@ "metadata": {}, "outputs": [], "source": [ - "\n", "anim = ana_results.plume_3d(\n", " animate=True,\n", " cmap='viridis')\n", diff --git a/examples/example_exact_equation.ipynb b/examples/example_exact_equation.ipynb index 667ebc8..e795b52 100644 --- a/examples/example_exact_equation.ipynb +++ b/examples/example_exact_equation.ipynb @@ -162,7 +162,7 @@ " model_parameters=model,\n", " verbose=True,\n", ")\n", - "mbt_nodecay.run()\n", + "nodecay_results = mbt_nodecay.run()\n", "\n", "# Set half life to a non-0 value to simulate linear decay\n", "att.half_life = 10 * 365\n", @@ -174,7 +174,7 @@ " model_parameters=model,\n", " verbose=True,\n", ")\n", - "mbt_lindecay.run()\n", + "lindecay_results = mbt_lindecay.run()\n", "\n", "mbt_instant = Mibitrans(\n", " hydrological_parameters=hydro,\n", @@ -184,7 +184,7 @@ " verbose=True,\n", ")\n", "mbt_instant.instant_reaction(electron_acceptors=ea)\n", - "mbt_instant.run()" + "instant_results = mbt_instant.run()" ] }, { @@ -202,9 +202,9 @@ "metadata": {}, "outputs": [], "source": [ - "centerline(mbt_nodecay, color=\"red\", label=\"No decay\")\n", - "centerline(mbt_lindecay, color=\"blue\", label=\"Linear decay\")\n", - "centerline(mbt_instant, color=\"green\", label=\"Instant reaction\")\n", + "centerline(nodecay_results, color=\"red\", label=\"No decay\")\n", + "centerline(lindecay_results, color=\"blue\", label=\"Linear decay\")\n", + "centerline(instant_results, color=\"green\", label=\"Instant reaction\")\n", "plt.legend()\n", "plt.show()" ] @@ -216,7 +216,7 @@ "metadata": {}, "outputs": [], "source": [ - "plume_3d(mbt_nodecay, cmap=\"viridis\")\n", + "plume_3d(nodecay_results, cmap=\"viridis\")\n", "plt.show()" ] }, @@ -243,7 +243,7 @@ " model_parameters=model,\n", " verbose=True,\n", ")\n", - "bio_nodecay.run()\n", + "bio_nodecay_results = bio_nodecay.run()\n", "\n", "# Set half life to a non-0 value to simulate linear decay\n", "att.half_life = 10 * 365\n", @@ -255,7 +255,7 @@ " model_parameters=model,\n", " verbose=True,\n", ")\n", - "bio_lindecay.run()\n", + "bio_lindecay_results = bio_lindecay.run()\n", "\n", "bio_instant = Bioscreen(\n", " hydrological_parameters=hydro,\n", @@ -265,7 +265,7 @@ " verbose=True,\n", ")\n", "bio_instant.instant_reaction(electron_acceptors=ea)\n", - "bio_instant.run()" + "bio_instant_results = bio_instant.run()" ] }, { @@ -275,12 +275,12 @@ "metadata": {}, "outputs": [], "source": [ - "centerline(mbt_nodecay, color=\"red\", label=\"No decay Mibitrans\")\n", - "centerline(mbt_lindecay, color=\"blue\", label=\"Linear decay Mibitrans\")\n", - "centerline(mbt_instant, color=\"green\", label=\"Instant reaction Mibitrans\")\n", - "centerline(bio_nodecay, color=\"tomato\", linestyle=\":\", label=\"No decay Bioscreen\")\n", - "centerline(bio_lindecay, color=\"lightblue\", linestyle=\":\", label=\"Linear decay Bioscreen\")\n", - "centerline(bio_instant, color=\"lightgreen\", linestyle=\":\", label=\"Instant reaction Bioscreen\")\n", + "centerline(nodecay_results, color=\"red\", label=\"No decay Mibitrans\")\n", + "centerline(lindecay_results, color=\"blue\", label=\"Linear decay Mibitrans\")\n", + "centerline(instant_results, color=\"green\", label=\"Instant reaction Mibitrans\")\n", + "centerline(bio_nodecay_results, color=\"tomato\", linestyle=\":\", label=\"No decay Bioscreen\")\n", + "centerline(bio_lindecay_results, color=\"lightblue\", linestyle=\":\", label=\"Linear decay Bioscreen\")\n", + "centerline(bio_instant_results, color=\"lightgreen\", linestyle=\":\", label=\"Instant reaction Bioscreen\")\n", "plt.title(\"Comparison between Mibitrans and Bioscreen, at t=1825 days.\")\n", "plt.legend()\n", "plt.show()" @@ -327,15 +327,15 @@ ")\n", "colors = [\"tomato\", \"sandybrown\", \"khaki\", \"greenyellow\", \"lightgreen\"]\n", "colors_bio = [\"red\", \"orange\", \"gold\", \"yellowgreen\", \"green\"]\n", - "for i in range(len(mbt_nodecay.t)):\n", - " centerline(mbt_nodecay, time=mbt_nodecay.t[i], color=colors[i], label=f\"Mibitrans, t={mbt_nodecay.t[i]}\")\n", + "for i in range(len(nodecay_results.t)):\n", + " centerline(nodecay_results, time=mbt_nodecay.t[i], color=colors[i], label=f\"Mibitrans, t={mbt_nodecay.t[i]}\")\n", " plt.plot(bio_x, bio_at_nodecay[i, :], color=colors_bio[i], linestyle=\":\", label=f\"bio_at, t={mbt_nodecay.t[i]}\")\n", "plt.legend()\n", "plt.show()\n", "\n", "\n", "for i in range(len(mbt_lindecay.t)):\n", - " centerline(mbt_lindecay, time=mbt_lindecay.t[i], color=colors[i], label=f\"Mibitrans, t={mbt_lindecay.t[i]}\")\n", + " centerline(lindecay_results, time=mbt_lindecay.t[i], color=colors[i], label=f\"Mibitrans, t={mbt_lindecay.t[i]}\")\n", " plt.plot(bio_x, bio_at_lindecay[i, :], color=colors_bio[i], linestyle=\":\", label=f\"bio_at, t={mbt_lindecay.t[i]}\")\n", "plt.legend()\n", "plt.show()" @@ -346,7 +346,7 @@ "id": "e9a6b6c841cf9302", "metadata": {}, "source": [ - "mibitrans output corresponds very well with the BIOSCREEN-AT output, and has higher resolution. Note that despite BIOSCREEN-AT taking in instant reaction parameters, it does not have the option to calculate biodegradation this way." + "mibitrans output corresponds very well with the BIOSCREEN-AT output, and has higher resolution. Note that despite BIOSCREEN-AT taking in instant reaction parameters, it does not have the option to calculate biodegradation this way, therefore, instant reaction model can not be compared." ] } ], diff --git a/examples/example_keesler.ipynb b/examples/example_keesler.ipynb index 7516843..b1f99cc 100644 --- a/examples/example_keesler.ipynb +++ b/examples/example_keesler.ipynb @@ -124,10 +124,10 @@ "metadata": {}, "outputs": [], "source": [ - "model_pars = mbt.ModelParameters(model_length=1000/ft,\n", + "model_pars = mbt.ModelParameters(model_length=500/ft,\n", " model_width=200/ft,\n", - " model_time=10*365,\n", - " dx=5/ft,\n", + " model_time=6*365,\n", + " dx=2/ft,\n", " dy=1/ft,\n", " dt=365/5)" ] @@ -288,7 +288,7 @@ "metadata": {}, "outputs": [], "source": [ - "time_point = 365 * 5\n", + "time_point = 365 * 6\n", "plt.figure()\n", "mbt_results_instant.centerline(time=time_point, label=\"Mibitrans\", color=\"blue\")\n", "ana_results_instant.centerline(time=time_point, label=\"Anatrans\", color=\"red\", linestyle=\"--\")\n", @@ -310,33 +310,91 @@ "outputs": [], "source": [ "new_hydrology = mbt.HydrologicalParameters(\n", - "\n", + " h_conductivity = 0.011 / 100 * 3600 * 24, #m/d\n", + " h_gradient = 0.003, # m/m\n", + " porosity = 0.3,\n", + " alpha_x = 32.5 / ft, #m\n", + " alpha_y = 3.25 / ft, #m\n", + " alpha_z = 0\n", ")\n", - "bio_model." + "mbt_model.hydrological_parameters = new_hydrology\n", + "ana_model.hydrological_parameters = new_hydrology\n", + "bio_model.hydrological_parameters = new_hydrology\n", + "mbt_model.mode = \"instant\"\n", + "ana_model.mode = \"instant\"\n", + "bio_model.mode = \"instant\"\n", + "bio_results_adapted = bio_model.run()\n", + "ana_results_adapted = ana_model.run()\n", + "mbt_results_adapted = mbt_model.run()\n", + "\n" ] }, { - "cell_type": "markdown", - "id": "9020b5a55a397f08", + "cell_type": "code", + "execution_count": null, + "id": "cdfc0b049e63ce9c", "metadata": {}, + "outputs": [], "source": [ - "According to Bioscreen, the above model predicts the measured field concentrations fairly well" + "time_point = 365 * 6\n", + "plt.figure()\n", + "mbt_results_adapted.centerline(time=time_point, label=\"Mibitrans\", color=\"blue\")\n", + "ana_results_adapted.centerline(time=time_point, label=\"Anatrans\", color=\"red\", linestyle=\"--\")\n", + "bio_results_adapted.centerline(time=time_point, label=\"Bioscreen\", color=\"green\", linestyle=\":\")\n", + "\n", + "plt.scatter(field_data_x, field_data_c, label=\"field data\", color=\"black\")\n", + "plt.title(\"Comparison instant reaction models to field data, t=6 years\")\n", + "plt.xlim(-5,150)\n", + "plt.legend()\n", + "plt.show()" ] }, { "cell_type": "code", "execution_count": null, - "id": "5ec341476e56cd1e", + "id": "43aa262a855fa057", "metadata": {}, "outputs": [], "source": [ + "half_life = 0.15*365\n", + "mbt_model.attenuation_parameters.half_life = half_life\n", + "ana_model.attenuation_parameters.half_life = half_life\n", + "bio_model.attenuation_parameters.half_life = half_life\n", + "mbt_model.mode = \"linear\"\n", + "ana_model.mode = \"linear\"\n", + "bio_model.mode = \"linear\"\n", + "bio_results_linear = bio_model.run()\n", + "ana_results_linear = ana_model.run()\n", + "mbt_results_linear = mbt_model.run()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35b3a013caff9f68", + "metadata": {}, + "outputs": [], + "source": [ + "time_point = 365 * 6\n", "plt.figure()\n", - "plt.plot(mbt_results_instant.x, mbt_results_instant.cxyt_noBC[-1,100,:])\n", - "plt.plot(ana_results_instant.x, ana_results_instant.cxyt_noBC[-1, 100, :])\n", - "plt.plot(bio_results_instant.x, bio_results_instant.cxyt_noBC[-1, 100, :])\n", + "mbt_results_linear.centerline(time=time_point, label=\"Mibitrans\", color=\"blue\")\n", + "ana_results_linear.centerline(time=time_point, label=\"Anatrans\", color=\"red\", linestyle=\"--\")\n", + "bio_results_linear.centerline(time=time_point, label=\"Bioscreen\", color=\"green\", linestyle=\":\")\n", + "plt.title(\"Comparison linear decay models to field data, t=6 years\")\n", + "plt.scatter(field_data_x, field_data_c, label=\"field data\", color=\"black\")\n", + "plt.xlim(-5,150)\n", + "plt.legend()\n", "plt.show()" ] }, + { + "cell_type": "markdown", + "id": "9020b5a55a397f08", + "metadata": {}, + "source": [ + "According to Bioscreen, the above model predicts the measured field concentrations fairly well" + ] + }, { "cell_type": "markdown", "id": "eb969db6ab0c1424", diff --git a/examples/example_mibitrans_anatrans_comparison.ipynb b/examples/example_mibitrans_anatrans_comparison.ipynb index f7862f7..bf03bee 100644 --- a/examples/example_mibitrans_anatrans_comparison.ipynb +++ b/examples/example_mibitrans_anatrans_comparison.ipynb @@ -3,18 +3,13 @@ { "cell_type": "code", "execution_count": null, - "id": "initial_id", + "id": "8e86b7c802bf511e", "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import numpy as np\n", - "from mibitrans.data.parameters import AttenuationParameters\n", - "from mibitrans.data.parameters import HydrologicalParameters\n", - "from mibitrans.data.parameters import ModelParameters\n", - "from mibitrans.data.parameters import SourceParameters\n", - "from mibitrans.transport.models import Anatrans\n", - "from mibitrans.transport.models import Mibitrans\n", + "import mibitrans as mbt\n", "from mibitrans.visualize.animation import animate_1d" ] }, @@ -64,11 +59,11 @@ { "cell_type": "code", "execution_count": null, - "id": "29cec10a2865e2ca", + "id": "b59009fc4543720f", "metadata": {}, "outputs": [], "source": [ - "hydro = HydrologicalParameters(\n", + "hydro = mbt.HydrologicalParameters(\n", " velocity=0.277, # Flow velocity [m/d]\n", " porosity=0.25, # Effective soil porosity [-]\n", " alpha_x=10, # Longitudinal dispersivity, in [m]\n", @@ -76,7 +71,7 @@ " alpha_z=0.1 # Transverse vertical dispersivity, in [m]\n", ")\n", "\n", - "att = AttenuationParameters(\n", + "att = mbt.AttenuationParameters(\n", " retardation=1,\n", " # Molecular diffusion, in [m2/day]\n", " diffusion=0,\n", @@ -84,14 +79,14 @@ " half_life=0#5*365\n", ")\n", "\n", - "source = SourceParameters(\n", + "source = mbt.SourceParameters(\n", " source_zone_boundary=np.array([10]),\n", " source_zone_concentration=np.array([11]),\n", " depth=2.5,\n", " total_mass=\"inf\"\n", ")\n", "\n", - "model = ModelParameters(\n", + "model = mbt.ModelParameters(\n", " # Model extent in the longitudinal (x) direction in [m].\n", " model_length = 800,\n", " # Model extent in the transverse horizontal (y) direction in [m].\n", @@ -110,21 +105,21 @@ { "cell_type": "code", "execution_count": null, - "id": "54e9e7e491c986fe", + "id": "69a3fbdc2a9feae8", "metadata": {}, "outputs": [], "source": [ - "mbt_object = Mibitrans(hydro, att, source, model)\n", + "mbt_object = mbt.Mibitrans(hydro, att, source, model)\n", "mbt_results = mbt_object.run()\n", "\n", - "ana_object = Anatrans(hydro, att, source, model)\n", + "ana_object = mbt.Anatrans(hydro, att, source, model)\n", "ana_results = ana_object.run()" ] }, { "cell_type": "code", "execution_count": null, - "id": "531981a24a7e1d8e", + "id": "af63a154159d99d0", "metadata": {}, "outputs": [], "source": [ @@ -210,7 +205,7 @@ { "cell_type": "code", "execution_count": null, - "id": "ae416dc23dd6b334", + "id": "97f04f852e9eb61b", "metadata": {}, "outputs": [], "source": [ @@ -219,14 +214,14 @@ "output_list_ana = []\n", "for par in parameter_list:\n", " print(f\"starting run with par = {par}\")\n", - " mbt_object_alpha = Mibitrans(hydro, att, source, model)\n", + " mbt_object_alpha = mbt.Mibitrans(hydro, att, source, model)\n", " mbt_object_alpha.hydrological_parameters.alpha_x = par\n", " mbt_object_alpha.hydrological_parameters.alpha_y = par / 10\n", " mbt_object_alpha.hydrological_parameters.alpha_z = par / 100\n", " mbt_res_alp = mbt_object_alpha.run()\n", " output_list_mbt.append(mbt_res_alp.cxyt)\n", "\n", - " ana_object_alpha = Anatrans(hydro, att, source, model)\n", + " ana_object_alpha = mbt.Anatrans(hydro, att, source, model)\n", " ana_object_alpha.hydrological_parameters.alpha_x = par\n", " ana_object_alpha.hydrological_parameters.alpha_y = par / 10\n", " ana_object_alpha.hydrological_parameters.alpha_z = par / 100\n", @@ -237,7 +232,7 @@ { "cell_type": "code", "execution_count": null, - "id": "93a0274d7116f5f", + "id": "d3cc41c418dd5871", "metadata": {}, "outputs": [], "source": [ @@ -275,7 +270,7 @@ { "cell_type": "code", "execution_count": null, - "id": "2e8640aff2864ca7", + "id": "bda62df4b1341dfb", "metadata": {}, "outputs": [], "source": [ @@ -287,7 +282,7 @@ " parv = parameter_list_v[i]\n", " para = parameter_list_a[i]\n", " print(f\"starting run with parv = {parv} and para = {para}\")\n", - " mbt_va = Mibitrans(hydro, att, source, model)\n", + " mbt_va = mbt.Mibitrans(hydro, att, source, model)\n", " mbt_va.hydrological_parameters.velocity = parv\n", " mbt_va.hydrological_parameters.alpha_x = para\n", " mbt_va.hydrological_parameters.alpha_y = para / 10\n", @@ -295,7 +290,7 @@ " mbt_res_va = mbt_va.run()\n", " output_list_mbt_v.append(mbt_res_va.cxyt)\n", "\n", - " ana_va = Anatrans(hydro, att, source, model)\n", + " ana_va = mbt.Anatrans(hydro, att, source, model)\n", " ana_va.hydrological_parameters.velocity = parv\n", " ana_va.hydrological_parameters.alpha_x = para\n", " ana_va.hydrological_parameters.alpha_y = para / 10\n", @@ -307,7 +302,7 @@ { "cell_type": "code", "execution_count": null, - "id": "f5383ae968db9bf6", + "id": "df501e36f3fd38fc", "metadata": {}, "outputs": [], "source": [ diff --git a/examples/example_walkthrough.ipynb b/examples/example_walkthrough.ipynb index 2ed9933..7bcfd1c 100644 --- a/examples/example_walkthrough.ipynb +++ b/examples/example_walkthrough.ipynb @@ -328,6 +328,20 @@ "As the namesake model of this package, it is the recommended model to use. The other models introduce a margin of error by making some assumptions. However, since this model requires evaluation of an integral, computation time might be longer, depending on model discretization. The exact nature of these differences is too much to go into detail here, but is elaborated upon in the theoretical background.\n" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "$$\n", + "\\begin{align}\\tag{1}\n", + " C(x,y,z,t) &= \\sum_{i=1}^{n}\\left(C^*_{0,i}\\frac{x}{8\\sqrt{\\pi D^{'}_{x}}}\\exp(-\\gamma t) \\right. \\\\\n", + " &\\quad \\cdot \\int_{0}^{t}\\left[\\frac{1}{\\tau^{\\frac{3}{2}}} \\exp\\left((\\gamma - \\lambda_{eff})\\tau - \\frac{(x-v^{'}\\tau)^2}{4D^{'}_{x}\\tau}\\right) \\right. \\\\\n", + " &\\quad \\cdot \\left\\{\\operatorname{erfc}\\left(\\frac{y-Y_i}{2 \\sqrt{D^{'}_{y}\\tau}}\\right)-\\operatorname{erfc}\\left(\\frac{y+Y_i}{2 \\sqrt{D^{'}_{y}\\tau}}\\right) \\right\\} \\\\\n", + " &\\quad \\left. \\left. \\cdot \\left\\{\\operatorname{erfc}\\left(\\frac{z-Z}{2 \\sqrt{D^{'}_{z}\\tau}}\\right)-\\operatorname{erfc}\\left(\\frac{z+Z}{2 \\sqrt{D^{'}_{z}\\tau}}\\right) \\right\\}\\right] d\\tau \\right)\n", + "\\end{align}\n", + "$$" + ] + }, { "cell_type": "code", "execution_count": null, @@ -343,7 +357,6 @@ ")\n", "\n", "# Can use model object to request various attributes\n", - "print(\"x discretization: \", mbt_object.x)\n", "print(\"Retarded flow velocity: \", mbt_object.rv)\n", "print(\"x steps\", len(mbt_object.x), \"y steps\", len(mbt_object.y), \"t steps\", len(mbt_object.t))\n", "\n", @@ -429,7 +442,13 @@ " \"methane\":6.6,\n", " }\n", ")\n", - "print(mbt_object.biodegradation_capacity)\n", + "\n", + "# Or as a list\n", + "mbt_object.instant_reaction(\n", + " electron_acceptors=[1.65, 0.7, 16.6, 22.4, 6.6],\n", + " utilization_factor=[3.14, 4.9, 21.8, 4.7, 0.78]\n", + ")\n", + "\n", "# Note that using instant_reaction also resets the utilization factor to default.\n", "print(\"electron acceptor concentrations: \", mbt_object.electron_acceptors)\n", "print(\"electron acceptor utilization factors: \", mbt_object.utilization_factor)" @@ -439,7 +458,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Be aware that electron acceptor concentrations and utilization factors can only be changed using the instant_reaction method. Changing the properties directly will not work.\n", + "Be aware that electron acceptor concentrations and utilization factors should only be changed using the instant_reaction method. Changing the properties directly will not work.\n", "\n", "Now that instant reaction parameters are provided, the model can be run:" ] @@ -507,14 +526,19 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Other models\n", "\n", - "The two models other than the Mibitrans model share the same functionalities and properties as showcase above. The only difference is the implementation of calculation.\n", "\n", "#### Anatrans model\n", "The equation used for the Anatrans model has the assumption that C(x,y,z,t) = C(x,t) * C(y,t) * C(z,t). Then, the 3D ADE can be broken up in three separate differential equations which can be solved individually. For C(x,t) the solution is given in Bear (1979), C(y,t) and C(z,t) can be derived from Crank (1975). The Anatrans model is the combination of these solutions, with addition of source depletion, source superposition and instant reaction model, described in Newell et al. (1997). The solution of Newell et al. (1997) is based of the Domenico (1987) solution, a truncated version of the equation described above, which introduces an error with a size dependent on the ratio of flow velocity and longitudinal dispersivity. Anatrans instead uses the fully untruncated version." ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, { "cell_type": "code", "execution_count": null, From 4aea82499e3e53f10071f096a1f736dbb52073db Mon Sep 17 00:00:00 2001 From: Bakker Date: Tue, 3 Feb 2026 10:56:28 +0100 Subject: [PATCH 09/19] Simplified imput parameters Removed conversion from ft to m from notebook. Improved section title formatting. --- examples/example_keesler.ipynb | 10 +- ...xample_mibitrans_anatrans_comparison.ipynb | 2 +- examples/example_walkthrough.ipynb | 221 +++++++++--------- 3 files changed, 127 insertions(+), 106 deletions(-) diff --git a/examples/example_keesler.ipynb b/examples/example_keesler.ipynb index b1f99cc..5268342 100644 --- a/examples/example_keesler.ipynb +++ b/examples/example_keesler.ipynb @@ -232,7 +232,7 @@ "id": "8c2ca032cca0abb4", "metadata": {}, "source": [ - "Over time, the source concentration for the Bioscreen model increases to the correct value, and the results resolve to that of the Anatrans model. Differences between the Anatrans and Mibitrans model decrease" + "Over time, the source concentration for the Bioscreen model increases to the expected value, and the results resolve to that of the Anatrans model. Differences between the Anatrans and Mibitrans model decrease when plume front is transported away from source." ] }, { @@ -261,6 +261,14 @@ "mbt_results_instant = mbt_model.run()" ] }, + { + "cell_type": "markdown", + "id": "15da159822122e75", + "metadata": {}, + "source": [ + "At the plume fringes, electron acceptor concentrations are high enough to allow for full biodegradation of the" + ] + }, { "cell_type": "code", "execution_count": null, diff --git a/examples/example_mibitrans_anatrans_comparison.ipynb b/examples/example_mibitrans_anatrans_comparison.ipynb index bf03bee..55d45da 100644 --- a/examples/example_mibitrans_anatrans_comparison.ipynb +++ b/examples/example_mibitrans_anatrans_comparison.ipynb @@ -366,7 +366,7 @@ "plt.ylabel(r\"Concentration [$g/m^3$]\")\n", "plt.xlim((0,350))\n", "plt.show()\n", - "ani.save(\"centerline_animation.gif\", fps=10)\n" + "ani.save(\"centerline_animation.gif\", fps=10)" ] }, { diff --git a/examples/example_walkthrough.ipynb b/examples/example_walkthrough.ipynb index 7bcfd1c..1ded41f 100644 --- a/examples/example_walkthrough.ipynb +++ b/examples/example_walkthrough.ipynb @@ -1,26 +1,19 @@ { "cells": [ { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "import mibitrans as mbt" + "# Features of mibitrans" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The showcase of mibitrans below uses example data originating from BIOSCREEN, the Excel based modeling software this package took inspiration from. Describes field conditions on the Keesler Air Force Base in Mississippi, USA. Movement of mixed BTEX plume from 1989 to 1995. For more details, see Newell et al. (1997)\n", + "This example present the features implemented in the mibitrans package, and explains how to use them. The showcase uses example data originating from BIOSCREEN, the Excel based modeling software this package took inspiration from. Describes field conditions on the Keesler Air Force Base in Mississippi, USA. Movement of mixed BTEX plume from 1989 to 1995 due to leakage of underground storage tanks. For more details, see Newell et al. (1997). The example_keesler notebook goes into detail of origin of parameters and how to use mibitrans to investigate field sites.\n", "\n", - "As it was developed in the USA, length units are in ft. We'll use a conversion factor to express length in m instead.\n", - "\n", - "Newell, C. J., McLeod, R. K., & Gonzales, J. R. (1997). BIOSCREEN natural attenuation decision support\n", - "system version 1.4 revisions, Tech. rep., U.S. EPA." + "As it was developed in the USA, original units are in ft. They have been converted and rounded to m for sake of clarity." ] }, { @@ -29,25 +22,27 @@ "metadata": {}, "outputs": [], "source": [ - "ft = 3.281 # factor to convert ft to m" + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import mibitrans as mbt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Input by dataclasses\n", + "## Input by dataclasses\n", "\n", - "mibitrans use dataclasses located in mibitrans.data.read to handle data input. This can be There are five input dataclasses, each for a different category of parameters. To avoid any mistakes, units of input parameters should be the ones specified. When using different units, make sure that they are consistent throughout the entire modelling process." + "mibitrans use dataclasses located in mibitrans.data.read to handle data input. This can be There are five input dataclasses, each for a different category of parameters. To avoid any mishaps, units of input parameters should be the ones specified in each section. When using different units, make sure that they are consistent throughout the entire modelling process." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "#### Hydrological parameters\n", + "### Hydrological parameters\n", "\n", - "Contains parameters that are inherent to the aquifer properties; flow velocity, porosity and dispersivity. Flow velocity can alternatively be calculated from the hydraulic conductivity and hydraulic gradient. " + "Contains parameters that are inherent to the aquifer properties; flow velocity, porosity and dispersivity. Flow velocity can alternatively be calculated from the hydraulic conductivity and hydraulic gradient." ] }, { @@ -57,10 +52,10 @@ "outputs": [], "source": [ "hydro = mbt.HydrologicalParameters(\n", - " velocity=113.8/ft/365, # Groundwater flow velocity, in [m/day]\n", + " velocity=0.1, # Groundwater flow velocity, in [m/day]\n", " porosity=0.25, # Effective soil porosity [-]\n", - " alpha_x=13.3/ft, # Longitudinal dispersivity, in [m]\n", - " alpha_y=1.3/ft, # Transverse horizontal dispersivity, in [m]\n", + " alpha_x=4.1, # Longitudinal dispersivity, in [m]\n", + " alpha_y=0.4, # Transverse horizontal dispersivity, in [m]\n", " alpha_z=0 # Transverse vertical dispersivity, in [m]\n", ")\n", "\n", @@ -69,8 +64,8 @@ " h_gradient=0.048, # Hydraulic gradient [-]\n", " h_conductivity=0.495, # Hydraulic conductivity [m/day]\n", " porosity=0.25, # Effective soil porosity [-]\n", - " alpha_x=13.3/ft, # Longitudinal dispersivity, in [m]\n", - " alpha_y=1.3/ft, # Transverse horizontal dispersivity, in [m]\n", + " alpha_x=4.1, # Longitudinal dispersivity, in [m]\n", + " alpha_y=0.4, # Transverse horizontal dispersivity, in [m]\n", " alpha_z=0 # Transverse vertical dispersivity, in [m]\n", ")\n", "\n", @@ -86,10 +81,10 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Attenuation parameters\n", + "### Attenuation parameters\n", "Handles all parameters related to adsorption, diffusion and degradation.\n", "\n", - "##### Attenuation: retardation and diffusion\n", + "#### Attenuation: retardation and diffusion\n", "Retardation can be simply given as a value >= 1. Alternatively, the adsorption is calculated from the soil bulk density, paratition coefficient and the fraction of organic carbon in the soil. Note that calculation of retardation factor requires porosity as well, which is already provided in HydrologicalParameters. The calculation of retardation will therefore be automatically performed in the analytical equation. It can be manually calculated using the calculate_retardation method as well.\n", "\n", "Here, molecular diffusion can be given as well. It is 0 by default." @@ -134,7 +129,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "##### Attenuation: degradation parameters\n", + "#### Attenuation: degradation parameters\n", "\n", "Linear decay models only need either the contaminant decay rate or half life. Input for the instant reaction model is somewhat more involved, and is therefore done with a class method of the model classes." ] @@ -161,7 +156,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "For the examples below, we'll set the attenuation parameters to the desired settings" + "For the examples below, we'll set the attenuation parameters to these settings" ] }, { @@ -188,7 +183,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "##### Source parameters\n", + "### Source parameters\n", "\n", "Takes input of the dimensions of and concentrations at the contaminant source. The source is treated as a seperate phase, which dissolves into the groundwater over time. The source is assumed to be in symmetrical in its center, and concentrations decrease from the center to the fringes. Furthermore, the source is assumed to be constant over its depth. The transverse horizontal dimension (or width) of the source is divided into zones, which span a certain distance measured from the center of the source, each with an associated concentration. For example, a source can have a concentration of $10g/m^3$ $7m$ left and right from the source center, and a concentration of $5g/m^3$ up to $20m$ from the source center. This would then be entered as source_zone_boundary = $[7,20]$ and source_zone_concentration = $[10,5]$. The source can be a single zone with a single concentration as well.\n", "\n", @@ -201,24 +196,24 @@ "metadata": {}, "outputs": [], "source": [ - "# Input for a simple source zone with a width of 19.8m (65ft) and a continuous input (infinite source mass)\n", + "# Input for a simple source zone with a width of 20m and a continuous input (infinite source mass)\n", "source = mbt.SourceParameters(\n", " # Source zone boundaries, in [m] (simply using a float instead of a numpy array for\n", " # single source zone input will work as well)\n", - " source_zone_boundary=np.array([65/ft]),\n", + " source_zone_boundary=np.array([20]),\n", " # Source zone concentrations, in [g/m^3]\n", " source_zone_concentration=np.array([5]),\n", " # Source depth extent, in [m]\n", - " depth=10/ft,\n", - " # Source mass, considered infinite\n", + " depth=3,\n", + " # Source mass, in [g] or as np.inf / \"infinite\"\n", " total_mass=\"inf\"\n", ")\n", "\n", "# Alternatively, specify a source mass to allow for source decay\n", "source = mbt.SourceParameters(\n", - " source_zone_boundary=np.array([7/ft, 37/ft, 65/ft]),\n", + " source_zone_boundary=np.array([2, 11, 20]),\n", " source_zone_concentration=np.array([13.68, 2.508, 0.057]),\n", - " depth=10/ft,\n", + " depth=3,\n", " total_mass=2000000\n", ")\n", "\n", @@ -231,7 +226,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "##### Model parameters\n", + "### Model parameters\n", "\n", "Accepts input for the model dimensions and discretization. Model length is the extent in the (x) direction parallel to the groundwater flow direction. The model width is the extent of the model perpendicular (y) to the groundwater flow direction. Step size of the spatial dimensions is handled with dx and dy. Ensure that the source zone fits inside of the given model width. If step sizes are not given, a ratio of model_length (1/100), model_width (1/50) and model_time (1/10) is used by default." ] @@ -244,15 +239,15 @@ "source": [ "model = mbt.ModelParameters(\n", " # Model extent in the longitudinal (x) direction in [m].\n", - " model_length = 320/ft,\n", + " model_length = 100,\n", " # Model extent in the transverse horizontal (y) direction in [m].\n", - " model_width = 100/ft,\n", + " model_width = 50,\n", " # Model duration in [days].\n", " model_time = 6 * 365,\n", " # Model grid discretization step size in the longitudinal (x) direction, in [m].\n", - " dx = 1/ft,\n", + " dx = 0.5,\n", " # Model grid discretization step size in the transverse horizontal (y) direction, in [m].\n", - " dy = 1/ft,\n", + " dy = 0.5,\n", " # Model time discretization step size, in [days]\n", " dt = 365 / 5\n", ")" @@ -262,9 +257,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "##### Input checking\n", + "### Input checking\n", "\n", - "The dataclass inputs evaluates if the required input parameters are present, if they are of the correct data type and if they are in the expected domain." + "The dataclass inputs evaluates if the required input parameters are present, if they are of the correct data type and if they are in the expected domain.\n", + "\n", + "**!The cells below are intended to raise an error!** " ] }, { @@ -273,9 +270,9 @@ "metadata": {}, "outputs": [], "source": [ - "# If not all required parameters are specified, an error will be shown;\n", + "# Raise error if not all required parameters are specified;\n", "# HydrologicalParameters needs the porosity, longitudinal dispersivity and transverse horizontal dispersivity as well.\n", - "fake_hydro = mbt.HydrologicalParameters(velocity = 1)" + "bad_hydro = mbt.HydrologicalParameters(velocity = 1)" ] }, { @@ -284,9 +281,9 @@ "metadata": {}, "outputs": [], "source": [ - "# If the input datatype is not what is expected; source_zone_concentration expects a numpy array\n", - "# of the same length as the array given in source_zone_boundary.\n", - "fake_source = mbt.SourceParameters(\n", + "# Raise error if the input datatype is not what is expected;\n", + "# source_zone_concentration expects a numpy array of the same length as the array given in source_zone_boundary.\n", + "bad_source = mbt.SourceParameters(\n", " source_zone_boundary = np.array([1,2,3]),\n", " source_zone_concentration = \"this is a string, not an array\",\n", " depth = 10,\n", @@ -300,8 +297,9 @@ "metadata": {}, "outputs": [], "source": [ - "# Same goes for if the input parameter has a value outside its valid domain; retardation should have a value >= 1\n", - "fake_att = mbt.AttenuationParameters(\n", + "# Raise error if a parameter has a value outside its valid domain;\n", + "# Retardation should have a value >= 1\n", + "bad_att = mbt.AttenuationParameters(\n", " retardation = 0.1\n", ")" ] @@ -310,7 +308,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Analytical models\n", + "## Analytical models\n", "\n", "The various models located in mibitrans.transport.models. Currently, three models are implemented; 'Mibitrans', 'Anatrans' and 'Bioscreen'. Each with a distinct analytical solution, which are introduced below.\n", "\n", @@ -321,7 +319,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Mibitrans model\n", + "### Mibitrans model\n", "\n", "The Mibitrans model class uses the exact analytical solution implemented by Karanovic (2007) in the Excel based BIOSCREEN-AT, and added source depletion, akin to that implemented in its predecessor BIOSCREEN by Newell et al. (1997). This model is based on the Wexler (1992) solution. The Mibitrans model allows for the same method as used in BIOSCREEN-AT, but expands it by allowing multiple source zones (by means of superposition) and including the instant reaction model. These were present in the original BIOSCREEN, but not reimplemented in BIOSCREEN-AT. Using a single source zone in this model, and not using the instant reaction option will make the Mibitrans solution resolve to the equation described in Karanovic (2007). Which in turn resolves to the Wexler (1992) solution if source depletion is disabled.\n", "\n", @@ -360,24 +358,36 @@ "print(\"Retarded flow velocity: \", mbt_object.rv)\n", "print(\"x steps\", len(mbt_object.x), \"y steps\", len(mbt_object.y), \"t steps\", len(mbt_object.t))\n", "\n", - "# Run the model once initialized and obtain the results\n", + "# Run the model once initialized and obtain the results object\n", "mbt_results = mbt_object.run()\n" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Model results\n", + "\n", + "All models return a Results object when run. This object is a snapshot of the parameters used to generate the concentration distribution. This ensures that it is always clear which conditions lead to which result, even when re-running the model. Furthermore, the object has plotting functionalities and can generate a mass balance. This is further elaborated upon in the Visualization and Mass Balance section respectively." + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "# Once the model has run, results are contained in the cxyt attribute\n", - "model_cxyt = mbt_object.cxyt\n", + "# Concentration distribution is contained in the cxyt attribute\n", + "model_cxyt = mbt_results.cxyt\n", "\n", "# cxyt is indexed as [time, y-position, x-position]\n", "# Thus to get the concentration at the last time step, in the center of the plume for all x:\n", "plume_center = model_cxyt[-1, 132//2, :]\n", "\n", - "# mibitrans has build-in visualization methods (see visualization section for more details).\n", + "# The Results object also contains all information of parameters used to generate the output contained within\n", + "print(\"The flow velocity of the model was:\", mbt_results.hydrological_parameters.velocity, \"m/d\")\n", + "\n", + "# The Results object has build-in visualization methods (see visualization section for more details).\n", "mbt_results.centerline()\n", "plt.show()" ] @@ -386,6 +396,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "### Instant reaction model\n", + "\n", "To perform the model with biodegradation modelled as an instant reaction, use the instant_reaction class method. Input for the instant reaction model is somewhat more involved, needing electron donor and acceptor concentrations. For utilization factors (amount of electron donor/acceptor used/generated by biodegradation), the values for BTEX degradation are used by default, but custom values can be given. For more specifics about the underlying principles and assumptions, see the theory." ] }, @@ -487,6 +499,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "### Sample\n", "Use the sample method to get the concentration at a specific location and time." ] }, @@ -496,7 +509,7 @@ "metadata": {}, "outputs": [], "source": [ - "# Instead, sample a specific location at any point along the plume and any point in time\n", + "# Instead of distribution, sample a specific location at any point along the plume and any point in time\n", "concentration = mbt_object.sample(\n", " x_position=100,\n", " y_position=0,\n", @@ -528,17 +541,10 @@ "source": [ "\n", "\n", - "#### Anatrans model\n", + "### Anatrans model\n", "The equation used for the Anatrans model has the assumption that C(x,y,z,t) = C(x,t) * C(y,t) * C(z,t). Then, the 3D ADE can be broken up in three separate differential equations which can be solved individually. For C(x,t) the solution is given in Bear (1979), C(y,t) and C(z,t) can be derived from Crank (1975). The Anatrans model is the combination of these solutions, with addition of source depletion, source superposition and instant reaction model, described in Newell et al. (1997). The solution of Newell et al. (1997) is based of the Domenico (1987) solution, a truncated version of the equation described above, which introduces an error with a size dependent on the ratio of flow velocity and longitudinal dispersivity. Anatrans instead uses the fully untruncated version." ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "code", "execution_count": null, @@ -561,7 +567,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Bioscreen model\n", + "### Bioscreen model\n", "\n", "This model is an exact implementation of the transport equations implemented in the BIOSCREEN screening model of Newell et al. (1997), which is based on the Domenico (1987) analytical model. Using a truncated version of the equation used in the Anatrans model. This model is implemented as a method of comparison with the original BIOSCREEN software. And is included for legacy reasons, since it is the first model implemented in the mibitrans package, serving as a basis for the other models. However, caution should be taken when using this model, since a varying error is introduced by using the truncated analytical solution. The error is most prominent for shorter times and distances from the source, and depends on the ratio of flow velocity and longitudinal dispersivity. For modelling, the Anatrans (untruncated approximate solution) and Mibitrans (exact analytical solution) models are recommended instead." ] @@ -583,28 +589,6 @@ "plt.show()" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The model class check if they receive all required and the correct input dataclasses, ensuring model calculations will be performed without error." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Will not work; hydrological_parameters should be a HydrologicalParameters class object.\n", - "fake_mbt = mbt.Mibitrans(\n", - " hydrological_parameters=model,\n", - " attenuation_parameters=att,\n", - " source_parameters=source,\n", - " model_parameters=model\n", - ")" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -620,7 +604,7 @@ "metadata": {}, "outputs": [], "source": [ - "# Lets make some different model objects to use in the plotting, takes a couple of seconds to run\n", + "# Make some different model objects to use in the plotting, takes a couple of seconds to run\n", "\n", "#Mibitrans model\n", "mbt_object = mbt.Mibitrans(hydro, att, source, model)\n", @@ -650,15 +634,16 @@ "bio_nodecay = bio_object.run()\n", "\n", "bio_object.instant_reaction(electron_acceptors=ea)\n", - "bio_instant = bio_object.run()\n", - "\n" + "bio_instant = bio_object.run()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "#### Plotting centerline" + "### Centerline\n", + "\n", + "The centerline plot is a concentration distribution in the longitudinal (x) direction, over center of the plume by default (y=0). y-position along the plume and point of time can be adapted with function arguments." ] }, { @@ -667,7 +652,7 @@ "metadata": {}, "outputs": [], "source": [ - "# Pass model object to plotting function, by default, the last time step is used.\n", + "# Pass result object to plotting function, by default, the last time step is used.\n", "mbt.centerline(mbt_nodecay)\n", "plt.show()\n", "\n", @@ -696,7 +681,7 @@ "# Calling function separately per model gives more control over plot layout\n", "mbt.centerline(mbt_nodecay, time=6*365, linestyle=\"--\", color=\"green\", label=legend[0])\n", "mbt.centerline(mbt_lineardecay, time=6*365, linestyle=\"-.\", color=\"red\", label=legend[1])\n", - "plt.title(\"No and linear degradation Mibitrans model at plume center, t=6years\")\n", + "plt.title(\"No- and linear degradation Mibitrans model at plume center, t=6years\")\n", "plt.legend()\n", "plt.show()\n", "\n", @@ -713,7 +698,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Plotting transverse distribution" + "### Transverse distribution\n", + "Transverse plot visualizes the concentration distribution in the horizontal transverse direction, at a specified x-location along the plume." ] }, { @@ -739,7 +725,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Plotting breakthrough curve" + "### Breakthrough curve\n", + "Breakthrough curve shows concentration change over time for a specified point in the contaminant plume." ] }, { @@ -759,7 +746,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Plotting in 2D" + "### 2-dimensional plume\n", + "Using plume_2d, concentration distribution at desired point in time is visualized as a mesh." ] }, { @@ -775,7 +763,7 @@ "\n", "# Function passes plt.colormesh keyword arguments\n", "mbt.plume_2d(mbt_lineardecay, time=6*365, cmap=\"magma\")\n", - "plt.title(\"Contaminant plume with linear degradation Domenico model, t = 6 years\")\n", + "plt.title(\"Contaminant plume with linear degradation Mibitrans model, t = 6 years\")\n", "plt.show()\n", "\n", "# Once again also can be accessed through the class method\n", @@ -787,7 +775,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Plotting 2D in 3D" + "### 3-dimensional plume\n", + "\n", + "Using plot_3d, concentration distribution at desired point in time is visualized as a three-dimensional plume, with the vertical axis representing the concentration." ] }, { @@ -818,7 +808,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Animate plots\n", + "### Animate plots\n", "\n", "All plots mentioned above in the visualization section have the option to be animated, which also can be saved as a file. Multiple models can be combined in a single animation. Make sure that parameters passed to the functions are inside the domain of all models. As each animation frame is a model time step, all models should have the exact same dt, otherwise, the animation will not show the correct temporal change in concentration." ] @@ -840,8 +830,7 @@ "outputs": [], "source": [ "# Output needs to be assigned to variable for animation to work\n", - "ani = mbt.centerline([mbt_nodecay, mbt_lineardecay, mbt_instant], time=6*365, legend_names=legend, animate=True)\n", - "plt.show()" + "ani = mbt.centerline([mbt_nodecay, mbt_lineardecay, mbt_instant], time=6*365, legend_names=legend, animate=True)" ] }, { @@ -851,8 +840,7 @@ "outputs": [], "source": [ "# Animation of breakthrough curve instead shows a timelapse of drawing of each curve\n", - "ani1 = mbt.breakthrough([bio_nodecay, bio_lineardecay, bio_instant], x_position=20, legend_names=legend, animate=True)\n", - "plt.show()" + "ani1 = mbt.breakthrough([bio_nodecay, bio_lineardecay, bio_instant], x_position=20, legend_names=legend, animate=True)" ] }, { @@ -863,8 +851,7 @@ "source": [ "# For the 3d surface plot, an entirely new plot needs to be generated per time step\n", "# This can cause slightly longer execution times\n", - "ani2 = ana_instant.plume_3d(time=6*365, animate=True, cmap=\"viridis\")\n", - "plt.show()" + "ani2 = ana_instant.plume_3d(time=6*365, animate=True, cmap=\"viridis\")" ] }, { @@ -910,7 +897,7 @@ "metadata": {}, "outputs": [], "source": [ - "mbt_object.model_parameters.model_length = 500 / ft\n", + "mbt_object.model_parameters.model_length = 150\n", "results = mbt_object.run()\n", "mb = results.mass_balance()" ] @@ -928,8 +915,18 @@ "metadata": {}, "outputs": [], "source": [ + "plt.clf()\n", "results.centerline()\n", - "plt.show()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "plt.clf()\n", "results.transverse(20, label=\"x = 20m\")\n", "results.transverse(60, label=\"x = 60m\")\n", "results.transverse(100, label=\"x = 100m\")\n", @@ -958,6 +955,22 @@ "plt.title(\"Degraded plume mass of instant reaction model, compared to no decay.\")\n", "plt.show()" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Sources\n", + "\n", + "Newell, C. J., McLeod, R. K., & Gonzales, J. R. (1997). BIOSCREEN natural attenuation decision support\n", + "system version 1.4 revisions, Tech. rep., U.S. EPA.\n", + "\n", + "Karanovic\n", + "\n", + "Wexler\n", + "\n", + "Domenico" + ] } ], "metadata": { From 435edcf98d0c403fb714ca9cc265b3cb7bb61798 Mon Sep 17 00:00:00 2001 From: Bakker Date: Tue, 3 Feb 2026 11:10:18 +0100 Subject: [PATCH 10/19] Fix notebook linting --- examples/example_equation_formulation.ipynb | 30 ++++++++----------- examples/example_keesler.ipynb | 18 +++++++---- ...xample_mibitrans_anatrans_comparison.ipynb | 6 ++-- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/examples/example_equation_formulation.ipynb b/examples/example_equation_formulation.ipynb index 4198275..18ca9df 100644 --- a/examples/example_equation_formulation.ipynb +++ b/examples/example_equation_formulation.ipynb @@ -9,14 +9,8 @@ "source": [ "import matplotlib.pyplot as plt\n", "import numpy as np\n", - "from mibitrans.data.parameters import AttenuationParameters\n", - "from mibitrans.data.parameters import HydrologicalParameters\n", - "from mibitrans.data.parameters import ModelParameters\n", - "from mibitrans.data.parameters import SourceParameters\n", - "from mibitrans.transport.models import Anatrans\n", - "from mibitrans.visualize.plot_line import centerline\n", - "from scipy.special import erfcx, erf, erfc\n", - "import mibitrans as mbt" + "from scipy.special import erf\n", + "import mibitrans as mbt\n" ] }, { @@ -27,7 +21,7 @@ "outputs": [], "source": [ "ft = 3.281\n", - "hydro = HydrologicalParameters(\n", + "hydro = mbt.HydrologicalParameters(\n", " velocity=350/ft/365,\n", " h_gradient=0.048, # Hydraulic gradient [-]\n", " h_conductivity=0.495, # Hydraulic conductivity [m/day]\n", @@ -37,7 +31,7 @@ " alpha_z=0 # Transverse vertical dispersivity, in [m]\n", ")\n", "\n", - "att = AttenuationParameters(\n", + "att = mbt.AttenuationParameters(\n", " # Soil bulk density in [g/m^3]\n", " bulk_density=1.7,\n", " # Partition coefficient of the transported contaminant to soil organic matter, in [m^3/g]\n", @@ -50,14 +44,14 @@ " half_life=0\n", ")\n", "\n", - "source = SourceParameters(\n", + "source = mbt.SourceParameters(\n", " source_zone_boundary=np.array([30/ft, 35/ft, 40/ft]),\n", " source_zone_concentration=np.array([13.68, 2.508, 0.057]),\n", " depth=10/ft,\n", " total_mass=\"inf\"\n", ")\n", "\n", - "model = ModelParameters(\n", + "model = mbt.ModelParameters(\n", " # Model extent in the longitudinal (x) direction in [m].\n", " model_length = 1000/ft,\n", " # Model extent in the transverse horizontal (y) direction in [m].\n", @@ -80,7 +74,7 @@ "metadata": {}, "outputs": [], "source": [ - "class AnatransAlternativeSource(Anatrans):\n", + "class AnatransAlternativeSource(mbt.Anatrans):\n", " \"\"\"Mibitrans model class with alternative calculations.\"\"\"\n", "\n", " def __init__(\n", @@ -127,7 +121,7 @@ "metadata": {}, "outputs": [], "source": [ - "ana_object = Anatrans(hydro, att, source, model)\n", + "ana_object = mbt.Anatrans(hydro, att, source, model)\n", "ana_results = ana_object.run()\n", "\n", "alt_object = AnatransAlternativeSource(hydro, att, source, model)\n", @@ -151,7 +145,7 @@ "metadata": {}, "outputs": [], "source": [ - "plt.clf()\n", + "\n", "ana_results.centerline(color=\"green\")\n", "alt_results.centerline(color=\"red\", linestyle=\"--\")\n", "plt.show()" @@ -165,7 +159,7 @@ "outputs": [], "source": [ "plt.clf()\n", - "anim = centerline(model=[ana_results, alt_results],\n", + "anim = mbt.centerline(model=[ana_results, alt_results],\n", " legend_names=[\"Anatrans model\", \"Alternative model\"],\n", " linestyle=\":\",\n", " animate=True)\n", @@ -179,7 +173,7 @@ "metadata": {}, "outputs": [], "source": [ - "class AnatransAlternativeDispersion(Anatrans):\n", + "class AnatransAlternativeDispersion(mbt.Anatrans):\n", " \"\"\"Mibitrans model class with alternative calculations.\"\"\"\n", "\n", " def __init__(\n", @@ -247,7 +241,7 @@ "outputs": [], "source": [ "plt.clf()\n", - "anim = centerline(model=[ana_results, alt_results, alt_disp_results],\n", + "anim = mbt.centerline(model=[ana_results, alt_results, alt_disp_results],\n", " legend_names=[\"Anatrans model\", \"Alternative model\", \"Alt disp model\"],\n", " linestyle=\":\",\n", " animate=True)\n", diff --git a/examples/example_keesler.ipynb b/examples/example_keesler.ipynb index 5268342..bb38010 100644 --- a/examples/example_keesler.ipynb +++ b/examples/example_keesler.ipynb @@ -8,8 +8,8 @@ "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", - "import mibitrans as mbt\n", - "import numpy as np" + "import numpy as np\n", + "import mibitrans as mbt" ] }, { @@ -68,7 +68,7 @@ "outputs": [], "source": [ "att_pars = mbt.AttenuationParameters(\n", - " bulk_density=1.7, # kg/L (note; for consistency, should use g/m3, but since units cancel out here for calculation, using more intuitive kg/L instead)\n", + " bulk_density=1.7, # kg/L (note; for consistency, should use g/m3, but using more intuitive kg/L instead)\n", " partition_coefficient=38, # L/kg (units cancel out with bulk density)\n", " fraction_organic_carbon=0.000057,\n", " decay_rate = 0, # 1/day, for now, we do not consider linear decay rate.\n", @@ -242,7 +242,11 @@ "metadata": {}, "outputs": [], "source": [ - "anim = mbt.centerline([mbt_results, ana_results, bio_results], legend_names=[\"Mibitrans\", \"Anatrans\", \"Bioscreen\"], animate=True)\n", + "anim = mbt.centerline(\n", + " [mbt_results, ana_results, bio_results],\n", + " legend_names=[\"Mibitrans\", \"Anatrans\", \"Bioscreen\"],\n", + " animate=True\n", + ")\n", "plt.show()" ] }, @@ -276,7 +280,11 @@ "metadata": {}, "outputs": [], "source": [ - "anim = mbt.centerline([mbt_results_instant, ana_results_instant, bio_results_instant], legend_names=[\"Mibitrans\", \"Anatrans\", \"Bioscreen\"], animate=True)\n", + "anim = mbt.centerline(\n", + " [mbt_results_instant, ana_results_instant, bio_results_instant],\n", + " legend_names=[\"Mibitrans\", \"Anatrans\", \"Bioscreen\"],\n", + " animate=True\n", + ")\n", "plt.show()" ] }, diff --git a/examples/example_mibitrans_anatrans_comparison.ipynb b/examples/example_mibitrans_anatrans_comparison.ipynb index 55d45da..6075476 100644 --- a/examples/example_mibitrans_anatrans_comparison.ipynb +++ b/examples/example_mibitrans_anatrans_comparison.ipynb @@ -240,9 +240,11 @@ "colors_ana = [\"green\", \"greenyellow\", \"darkturquoise\", \"dodgerblue\", \"darkviolet\"]\n", "plt.figure()\n", "for i, conc in enumerate(output_list_mbt):\n", - " plt.plot(mbt_res_alp.x, conc[-1,75,:], color=colors_mbt[i], label=f\"Mibitrans a={parameter_list[i]}m\")\n", + " plt.plot(mbt_res_alp.x, conc[-1,75,:], color=colors_mbt[i],\n", + " label=f\"Mibitrans a={parameter_list[i]}m\")\n", "for i, conc in enumerate(output_list_ana):\n", - " plt.plot(ana_res_alp.x, conc[-1,75,:], color=colors_ana[i], linestyle=\"--\", label=f\"Anatrans a={parameter_list[i]}m\")\n", + " plt.plot(ana_res_alp.x, conc[-1,75,:], color=colors_ana[i], linestyle=\"--\",\n", + " label=f\"Anatrans a={parameter_list[i]}m\")\n", "plt.legend()\n", "plt.show()" ] From 970d8c20f74c80474aa1387d957e82e3b7b38b1e Mon Sep 17 00:00:00 2001 From: Bakker Date: Mon, 9 Feb 2026 08:18:16 +0100 Subject: [PATCH 11/19] Start at making comparitive error plots --- examples/example_kohler.ipynb | 8 ++ ...xample_mibitrans_anatrans_comparison.ipynb | 122 +++++++++++++----- 2 files changed, 98 insertions(+), 32 deletions(-) diff --git a/examples/example_kohler.ipynb b/examples/example_kohler.ipynb index b7dee18..7ad0c1e 100644 --- a/examples/example_kohler.ipynb +++ b/examples/example_kohler.ipynb @@ -12,6 +12,14 @@ "import mibitrans as mbt" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "362a06a996195edc", + "metadata": {}, + "outputs": [], + "source": [] + }, { "cell_type": "markdown", "id": "524f38b7b357f749", diff --git a/examples/example_mibitrans_anatrans_comparison.ipynb b/examples/example_mibitrans_anatrans_comparison.ipynb index 6075476..d60676b 100644 --- a/examples/example_mibitrans_anatrans_comparison.ipynb +++ b/examples/example_mibitrans_anatrans_comparison.ipynb @@ -19,7 +19,8 @@ "metadata": {}, "source": [ "# Exact solution versus untruncated solution\n", - "\n" + "\n", + "The mibitrans solution uses the exact solution of the 3D transport ADE, as described in Wexler et al. (1992). The anatrans solution approximates this solution by the method described in Domenico (1987), but without truncating the x part of the equation. Both are adapted for multiple source zones and source depletion, see equations below. The approximation for transverse dispersivity terms in the anatrans solution introduces an error, of which the size depends on parameter choices." ] }, { @@ -27,16 +28,17 @@ "id": "7e66c813a939d441", "metadata": {}, "source": [ - "## Truncated domenico solution\n", - "\n", - "$$\n", - "\\begin{align}\\tag{2}\n", - " C(x, y, z, t) &= \\frac{C_{0}}{8} \\exp \\left[ \\frac{x\\left(1-\\sqrt{1+4\\lambda \\alpha_x/v}\\right)}{2\\alpha_x}\\right] \\\\\n", - " &\\quad \\cdot \\operatorname{erfc} \\left[ \\frac{x - vt\\sqrt{1+4\\lambda \\alpha_x/v}}{2\\sqrt{\\alpha_x vt }} \\right] \\\\\n", - " &\\quad \\cdot \\left\\{ \\operatorname{erf} \\left[ \\frac{y + Y/2}{2\\sqrt{\\alpha_y x}} \\right] - \\operatorname{erf} \\left[ \\frac{y - Y/2}{2\\sqrt{\\alpha_y x)}} \\right] \\right\\} \\\\\n", - " &\\quad \\cdot \\Biggl. \\left\\{ \\operatorname{erf} \\left[ \\frac{z +Z/2}{2\\sqrt{\\alpha_z x)}} \\right] - \\operatorname{erf} \\left[ \\frac{z-Z/2}{2\\sqrt{\\alpha_z x}} \\right] \\right\\} \\nonumber\n", - "\\end{align}\n", - "$$" + "## Anatrans solution\n", + "untruncated Domenico (1987)\n", + "\\begin{aligned}\n", + " C(x, y, z, t) &= \\sum_{i=1}^{n}\\left\\{ \\frac{C^*_{0,i}}{8} \\exp \\left[-k_s \\left(t-\\frac{xR}{v} \\right)\\right] \\right. \\\\\n", + " &\\quad \\cdot \\left( \\exp \\left[ \\frac{x\\left(1-\\sqrt{1+4\\mu \\alpha_x/v}\\right)}{2\\alpha_x}\\right] \\right. \\\\\n", + " &\\quad \\quad \\cdot \\operatorname{erfc} \\left[ \\frac{x - vt\\sqrt{1+4\\mu \\alpha_x/v}}{2\\sqrt{\\alpha_x vt }} \\right] \\\\\n", + " &\\quad \\ \\, + \\exp \\left[ \\frac{x\\left(1+\\sqrt{1+4\\mu \\alpha_x/v}\\right)}{2\\alpha_x}\\right] \\\\\n", + " &\\quad \\quad \\cdot \\left. \\operatorname{erfc} \\left[ \\frac{x + vt\\sqrt{1+4\\mu \\alpha_x/v}}{2\\sqrt{\\alpha_x vt }} \\right] \\right) \\\\\n", + " &\\quad \\cdot \\left( \\operatorname{erf} \\left[ \\frac{y + Y_i/2}{2\\sqrt{\\alpha_y x}} \\right] - \\operatorname{erf} \\left[ \\frac{y - Y_i/2}{2\\sqrt{\\alpha_y x)}} \\right] \\right) \\\\\n", + " &\\quad \\cdot \\left. \\left( \\operatorname{erf} \\left[ \\frac{Z}{2\\sqrt{\\alpha_z x)}} \\right] - \\operatorname{erf} \\left[ \\frac{-Z}{2\\sqrt{\\alpha_z x}} \\right] \\right) \\right\\}\n", + "\\end{aligned}\n" ] }, { @@ -44,15 +46,17 @@ "id": "b773e32c9f5886da", "metadata": {}, "source": [ - "## Exact solution\n", - "\n", + "## Mibitrans solution\n", + "Wexler (1992)\n", "$$\n", - "\\begin{align}\\tag{1}\n", - " C(x,y,z,t) &= \\sum_{i=1}^{n}\\left(C^*_{0,i}\\frac{x}{8\\sqrt{\\pi D^{'}_{x}}}\\exp(-\\gamma t) \\right. \\\\\n", - " &\\quad \\cdot \\int_{0}^{t}\\left[\\frac{1}{\\tau^{\\frac{3}{2}}} \\exp\\left((\\gamma - \\lambda_{eff})\\tau - \\frac{(x-v^{'}\\tau)^2}{4D^{'}_{x}\\tau}\\right) \\right. \\\\\n", - " &\\quad \\cdot \\left\\{\\operatorname{erfc}\\left(\\frac{y-Y_i}{2 \\sqrt{D^{'}_{y}\\tau}}\\right)-\\operatorname{erfc}\\left(\\frac{y+Y_i}{2 \\sqrt{D^{'}_{y}\\tau}}\\right) \\right\\} \\\\\n", - " &\\quad \\left. \\left. \\cdot \\left\\{\\operatorname{erfc}\\left(\\frac{z-Z}{2 \\sqrt{D^{'}_{z}\\tau}}\\right)-\\operatorname{erfc}\\left(\\frac{z+Z}{2 \\sqrt{D^{'}_{z}\\tau}}\\right) \\right\\}\\right] d\\tau \\right)\n", - "\\end{align}\n", + "\\begin{equation}\\tag{2}\n", + "\\begin{aligned}\n", + " C(x,y,z,t) &= \\sum_{i=1}^{n}\\left(C^*_{0,i}\\frac{x}{8\\sqrt{\\pi \\alpha_{x}\\frac{v\\tau}{R}}}\\exp(-k_s t) \\right. \\\\\n", + " &\\quad \\cdot \\int_{0}^{t}\\left[\\frac{1}{\\tau^{\\frac{3}{2}}} \\exp\\left((k_s - \\mu)\\tau - \\frac{(x-\\frac{v\\tau}{R})^2}{4\\alpha_{x}\\frac{v\\tau}{R}}\\right) \\right. \\\\\n", + " &\\quad \\cdot \\left\\{\\operatorname{erfc}\\left(\\frac{y-Y_i}{2 \\sqrt{\\alpha_{y}\\frac{v\\tau}{R}}}\\right)-\\operatorname{erfc}\\left(\\frac{y+Y_i}{2 \\sqrt{\\alpha_{y}\\frac{v\\tau}{R}}}\\right) \\right\\} \\\\\n", + " &\\quad \\left. \\left. \\cdot \\left\\{\\operatorname{erfc}\\left(\\frac{-Z}{2 \\sqrt{\\alpha_{z}\\frac{v\\tau}{R}}}\\right)-\\operatorname{erfc}\\left(\\frac{Z}{2 \\sqrt{\\alpha_{z}\\frac{v\\tau}{R}}}\\right) \\right\\}\\right] d\\tau \\right)\n", + "\\end{aligned}\n", + "\\end{equation}\n", "$$" ] }, @@ -146,7 +150,7 @@ " linestyle=[\"-\", \"-\", \"-\", \"-\", \"--\", \"--\", \"--\", \"--\"])\n", "plt.xlabel(\"y-position [m]\")\n", "plt.ylabel(r\"Concentration [$g/m^3$]\")\n", - "ani.save(\"transverse_animation_2.gif\", fps=10)\n", + "#ani.save(\"transverse_animation_2.gif\", fps=10)\n", "plt.show()\n" ] }, @@ -277,7 +281,7 @@ "outputs": [], "source": [ "parameter_list_v = [0.1, 0.1, 0.1, 0.1, 0.1]\n", - "parameter_list_a = [10, 5, 2, 1, 0.5]\n", + "parameter_list_a = [0., 5, 2, 1, 0.5]\n", "output_list_mbt_v = []\n", "output_list_ana_v = []\n", "for i in range(len(parameter_list_v)):\n", @@ -290,7 +294,7 @@ " mbt_va.hydrological_parameters.alpha_y = para / 10\n", " mbt_va.hydrological_parameters.alpha_z = 0#para / 100\n", " mbt_res_va = mbt_va.run()\n", - " output_list_mbt_v.append(mbt_res_va.cxyt)\n", + " output_list_mbt_v.append(mbt_res_va.relative_cxyt)\n", "\n", " ana_va = mbt.Anatrans(hydro, att, source, model)\n", " ana_va.hydrological_parameters.velocity = parv\n", @@ -298,7 +302,7 @@ " ana_va.hydrological_parameters.alpha_y = para / 10\n", " ana_va.hydrological_parameters.alpha_z = 0#para / 100\n", " ana_res_va = ana_va.run()\n", - " output_list_ana_v.append(ana_res_va.cxyt)" + " output_list_ana_v.append(ana_res_va.relative_cxyt)" ] }, { @@ -358,17 +362,17 @@ " time_parameter=mbt_res_va.t,\n", " y_colors=[\"darkgreen\", \"limegreen\", \"cornflowerblue\", \"blue\", \"indigo\",\n", " \"green\", \"greenyellow\", \"darkturquoise\", \"dodgerblue\", \"violet\"],\n", - " y_names=[r\"Mibitrans $\\alpha_l=0.1$\", r\"Mibitrans $\\alpha_l=0.5$\", r\"Mibitrans $\\alpha_l=1$\",\n", - " r\"Mibitrans $\\alpha_l=5$\", r\"Mibitrans $\\alpha_l=10$\", r\"Anatrans $\\alpha_l=0.1$\",\n", - " r\"Anatrans $\\alpha_l=0.5$\", r\"Anatrans $\\alpha_l=1$\", r\"Anatrans $\\alpha_l=5$\",\n", - " r\"Anatrans $\\alpha_l=10$\"\n", + " y_names=[r\"Mibitrans $\\alpha_l=10$\", r\"Mibitrans $\\alpha_l=5$\", r\"Mibitrans $\\alpha_l=1$\",\n", + " r\"Mibitrans $\\alpha_l=0.5$\", r\"Mibitrans $\\alpha_l=0.1$\", r\"Anatrans $\\alpha_l=10$\",\n", + " r\"Anatrans $\\alpha_l=5$\", r\"Anatrans $\\alpha_l=1$\", r\"Anatrans $\\alpha_l=0.5$\",\n", + " r\"Anatrans $\\alpha_l=0.1$\",\n", " ],\n", " linestyle=[\"-\", \"-\", \"-\", \"-\", \"-\", \"--\", \"--\", \"--\", \"--\", \"--\"])\n", "plt.xlabel(\"x-position [m]\")\n", "plt.ylabel(r\"Concentration [$g/m^3$]\")\n", "plt.xlim((0,350))\n", "plt.show()\n", - "ani.save(\"centerline_animation.gif\", fps=10)" + "#ani.save(\"centerline_animation.gif\", fps=10)" ] }, { @@ -388,10 +392,10 @@ " time_parameter=mbt_res_va.t,\n", " y_colors=[\"darkgreen\", \"limegreen\", \"cornflowerblue\", \"blue\", \"indigo\",\n", " \"green\", \"greenyellow\", \"darkturquoise\", \"dodgerblue\", \"violet\"],\n", - " y_names=[r\"Mibitrans $\\alpha_l=0.1$\", r\"Mibitrans $\\alpha_l=0.5$\", r\"Mibitrans $\\alpha_l=1$\",\n", - " r\"Mibitrans $\\alpha_l=5$\", r\"Mibitrans $\\alpha_l=10$\", r\"Anatrans $\\alpha_l=0.1$\",\n", - " r\"Anatrans $\\alpha_l=0.5$\", r\"Anatrans $\\alpha_l=1$\", r\"Anatrans $\\alpha_l=5$\",\n", - " r\"Anatrans $\\alpha_l=10$\"\n", + " y_names=[\"Mibitrans $\\alpha_l=10$\", r\"Mibitrans $\\alpha_l=5$\", r\"Mibitrans $\\alpha_l=1$\",\n", + " r\"Mibitrans $\\alpha_l=0.5$\", r\"Mibitrans $\\alpha_l=0.1$\", r\"Anatrans $\\alpha_l=10$\",\n", + " r\"Anatrans $\\alpha_l=5$\", r\"Anatrans $\\alpha_l=1$\", r\"Anatrans $\\alpha_l=0.5$\",\n", + " r\"Anatrans $\\alpha_l=0.1$\",\n", " ],\n", " linestyle=[\"-\", \"-\", \"-\", \"-\", \"-\", \"--\", \"--\", \"--\", \"--\", \"--\"])\n", "plt.xlabel(\"y-position [m]\")\n", @@ -399,6 +403,60 @@ "#ani.save(\"transverse_animation.gif\", fps=10)\n", "plt.show()" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "67219dd9a181a53c", + "metadata": {}, + "outputs": [], + "source": [ + "from matplotlib import animation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a68f423fc54c093e", + "metadata": {}, + "outputs": [], + "source": [ + "plt.clf()\n", + "mbt_a10 = mbtv[0]\n", + "ana_a10 = anav[0]\n", + "\n", + "err_aprx_raw = (ana_a10 - mbt_a10) / mbt_a10 * 100\n", + "err_aprx_raw = np.where(np.isnan(err_aprx_raw), 0, err_aprx_raw)\n", + "err_aprx = np.log10(abs(err_aprx_raw))\n", + "err_abs = ana_a10 - mbt_a10\n", + "# print(err_aprx[-1,len(mbt_va.y)//2, :])\n", + "# plt.plot(mbt_va.x, mbt_a10[-1,len(mbt_va.y)//2, :], color = \"green\")\n", + "# plt.plot(mbt_va.x, ana_a10[-1,len(mbt_va.y)//2, :], color = \"red\")\n", + "# plt.show()\n", + "#\n", + "# plt.plot(mbt_va.x, err_aprx[-1,len(mbt_va.y)//2, :], color = \"blue\")\n", + "# plt.show()\n", + "# plt.pcolormesh(mbt_va.x, mbt_va.y, err_abs[10, :, :], cmap=\"jet\")\n", + "# plt.colorbar()\n", + "# plt.show()\n", + "\n", + "fig, ax = plt.subplots()\n", + "mesh = ax.pcolormesh(\n", + " mbt_va.x[:150], mbt_va.y[35:115], err_abs[0, 35:115, :150], vmin=-0.2, vmax=0.2, cmap=\"Spectral\"\n", + ")\n", + "cbar = fig.colorbar(mesh, ax=ax)\n", + "cbar.set_label(\"Absolute error in rel. concentration\")\n", + "ax.set_xlabel(\"Distance from source (m)\")\n", + "ax.set_ylabel(\"Distance from plume center (m)\")\n", + "\n", + "def update(frame):\n", + " mesh.set_array(err_abs[frame, 35:115, :150])\n", + " ax.set_title(f\"Concentration distribution at t={mbt_va.t[frame]} days\")\n", + " return mesh\n", + "\n", + "ani = animation.FuncAnimation(fig=fig, func=update, frames=len(mbt_va.t))\n", + "plt.show()\n" + ] } ], "metadata": { From 7b2c19711e3833b10ea7c4aadd8e367bd7b6fb86 Mon Sep 17 00:00:00 2001 From: Bakker Date: Mon, 9 Feb 2026 14:42:31 +0100 Subject: [PATCH 12/19] Fix error in source depletion term -k_source - decay_rate should be k_source - decay_rate. --- mibitrans/transport/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mibitrans/transport/models.py b/mibitrans/transport/models.py index 2473c6e..57444c1 100644 --- a/mibitrans/transport/models.py +++ b/mibitrans/transport/models.py @@ -132,7 +132,7 @@ def integrand(t, sz): / (t**3) * ( np.exp( - (-self.k_source - self._decay_rate) * t**4 + (self.k_source - self._decay_rate) * t**4 - (x_position - self.rv * t**4) ** 2 / (4 * self.disp_x * t**4) ) * ( @@ -208,7 +208,7 @@ def _equation_integrand(self, t, sz): def _equation_term_x(self, t): term = np.exp( - (-self.k_source - self._decay_rate) * t - (self.xxx[:, :, 1:] - self.rv * t) ** 2 / (4 * self.disp_x * t) + (self.k_source - self._decay_rate) * t - (self.xxx[:, :, 1:] - self.rv * t) ** 2 / (4 * self.disp_x * t) ) term[np.isnan(term)] = 0 return term From ff9c6740fc245793233bd2450cd694c68cf4383c Mon Sep 17 00:00:00 2001 From: Bakker Date: Mon, 9 Feb 2026 14:51:53 +0100 Subject: [PATCH 13/19] Changed decay sqrt to separate function Change increases adaptability of model class --- mibitrans/transport/models.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mibitrans/transport/models.py b/mibitrans/transport/models.py index 57444c1..b877f64 100644 --- a/mibitrans/transport/models.py +++ b/mibitrans/transport/models.py @@ -385,9 +385,12 @@ def _equation_term_y(self, i, xxx, yyy): term[np.isnan(term)] = 0 return term + def _equation_decay_sqrt(self): + return np.sqrt(1 + 4 * self._decay_rate * self._hyd_pars.alpha_x / self.rv) + def _calculate_concentration_for_all_xyt(self, xxx, yyy, ttt): cxyt = 0 - decay_sqrt = np.sqrt(1 + 4 * self._decay_rate * self._hyd_pars.alpha_x / self.rv) + decay_sqrt = self._equation_decay_sqrt() x_term = self._equation_term_x(xxx, ttt, decay_sqrt) additional_x = self._equation_term_additional_x(xxx, ttt, decay_sqrt) z_term = self._equation_term_z(xxx) @@ -482,7 +485,7 @@ def _calculate_concentration_for_all_xyt(self, xxx, yyy, ttt): # Difference with the Anatrans solution is the lack of additional term. cxyt = 0 with np.errstate(divide="ignore", invalid="ignore"): - decay_sqrt = np.sqrt(1 + 4 * self._decay_rate * self._hyd_pars.alpha_x / self.rv) + decay_sqrt = self._equation_decay_sqrt() x_term = self._equation_term_x(xxx, ttt, decay_sqrt) z_term = self._equation_term_z(xxx) source_decay = self._equation_term_source_decay(xxx, ttt) From cb33026da7fc1af3dbb18fd6894049dcc2af710b Mon Sep 17 00:00:00 2001 From: Bakker Date: Tue, 10 Feb 2026 11:07:05 +0100 Subject: [PATCH 14/19] Changed source depletion in Anatrans. Adapted tests to Keesler data Anatrans now uses the mathematically sound source depletion expression. Bioscreen still uses the expression as used in BIOSCREEN. All test parameters and data have been adapted to represent the Keesler site, in order to test on actual field data instead of dummy data. --- mibitrans/transport/model_parent.py | 5 +- mibitrans/transport/models.py | 26 +- tests/conftest.py | 50 ++- tests/test_anatrans.py | 8 +- tests/test_bioscreen.py | 8 +- tests/test_example_data.py | 662 +++++++++++++--------------- tests/test_mass_balance.py | 26 +- tests/test_mibitrans.py | 8 +- 8 files changed, 403 insertions(+), 390 deletions(-) diff --git a/mibitrans/transport/model_parent.py b/mibitrans/transport/model_parent.py index 8d57f15..be99029 100644 --- a/mibitrans/transport/model_parent.py +++ b/mibitrans/transport/model_parent.py @@ -15,7 +15,7 @@ class Transport3D(ABC): - """Parent class for all 3-dimensional analytical solutions.""" + """Parent class for all 3-dimensional analytical transport solutions.""" def __init__( self, hydrological_parameters, attenuation_parameters, source_parameters, model_parameters, verbose=False @@ -299,7 +299,7 @@ class Results: """Object that holds model results and input parameters for individual runs.""" def __init__(self, model): - """Records input parameters and resulting output based given model. + """Records input parameters and resulting output of given model run. Args: model (Transport3D): Model object from which to initialize results. Should be child class of Transport3D. @@ -652,6 +652,7 @@ def mass_balance(self, time="all", verbose=False): def _check_instant_reaction_acceptor_input(electron_acceptors, utilization_factor): + """Check if electron acceptor and utilization factor are of correct datatype. Then pass them to dataclasses.""" if isinstance(electron_acceptors, (list, np.ndarray)): electron_acceptors_out = ElectronAcceptors(*electron_acceptors) elif isinstance(electron_acceptors, dict): diff --git a/mibitrans/transport/models.py b/mibitrans/transport/models.py index b877f64..1ac6a22 100644 --- a/mibitrans/transport/models.py +++ b/mibitrans/transport/models.py @@ -374,10 +374,8 @@ def _equation_term_z(self, xxx): inner_term = self._src_pars.depth / (2 * np.sqrt(self._hyd_pars.alpha_z * xxx)) return erf(inner_term) - erf(-inner_term) - def _equation_term_source_decay(self, xxx, ttt): - term = np.exp(-self.k_source * (ttt - xxx / self.rv)) - # Term can be max 1; can not have 'generation' of solute ahead of advection. - return np.where(term > 1, 1, term) + def _equation_term_source_depletion(self, xxx, ttt): + return np.exp(-self.k_source * ttt) def _equation_term_y(self, i, xxx, yyy): div_term = 2 * np.sqrt(self._hyd_pars.alpha_y * xxx) @@ -386,7 +384,7 @@ def _equation_term_y(self, i, xxx, yyy): return term def _equation_decay_sqrt(self): - return np.sqrt(1 + 4 * self._decay_rate * self._hyd_pars.alpha_x / self.rv) + return np.sqrt(1 + 4 * (self._decay_rate - self.k_source) * self._hyd_pars.alpha_x / self.rv) def _calculate_concentration_for_all_xyt(self, xxx, yyy, ttt): cxyt = 0 @@ -394,10 +392,10 @@ def _calculate_concentration_for_all_xyt(self, xxx, yyy, ttt): x_term = self._equation_term_x(xxx, ttt, decay_sqrt) additional_x = self._equation_term_additional_x(xxx, ttt, decay_sqrt) z_term = self._equation_term_z(xxx) - source_decay = self._equation_term_source_decay(xxx, ttt) + source_depletion = self._equation_term_source_depletion(xxx, ttt) for i in range(len(self.c_source)): y_term = self._equation_term_y(i, xxx, yyy) - cxyt_step = 1 / 8 * self.c_source[i] * source_decay * (x_term + additional_x) * y_term * z_term + cxyt_step = 1 / 8 * self.c_source[i] * source_depletion * (x_term + additional_x) * y_term * z_term cxyt += cxyt_step if self._mode == "instant_reaction": self.cxyt_noBC = cxyt.copy() @@ -481,17 +479,25 @@ def short_description(self): """Short description of model type.""" return "Bioscreen model" + def _equation_term_source_depletion(self, xxx, ttt): + term = np.exp(-self.k_source * (ttt - xxx / self.rv)) + # Term can be max 1; can not have 'generation' of solute ahead of advection. + return np.where(term > 1, 1, term) + + def _equation_decay_sqrt(self): + return np.sqrt(1 + 4 * self._decay_rate * self._hyd_pars.alpha_x / self.rv) + def _calculate_concentration_for_all_xyt(self, xxx, yyy, ttt): - # Difference with the Anatrans solution is the lack of additional term. + # Difference with the Anatrans solution is the lack of additional term and alternative source decay cxyt = 0 with np.errstate(divide="ignore", invalid="ignore"): decay_sqrt = self._equation_decay_sqrt() x_term = self._equation_term_x(xxx, ttt, decay_sqrt) z_term = self._equation_term_z(xxx) - source_decay = self._equation_term_source_decay(xxx, ttt) + source_depletion = self._equation_term_source_depletion(xxx, ttt) for i in range(len(self.c_source)): y_term = self._equation_term_y(i, xxx, yyy) - cxyt_step = 1 / 8 * self.c_source[i] * source_decay * x_term * y_term * z_term + cxyt_step = 1 / 8 * self.c_source[i] * source_depletion * x_term * y_term * z_term cxyt += cxyt_step if self._mode == "instant_reaction": self.cxyt_noBC = cxyt.copy() diff --git a/tests/conftest.py b/tests/conftest.py index 6cea416..b7555a6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,19 +9,30 @@ from mibitrans.transport.models import Bioscreen from mibitrans.transport.models import Mibitrans +# Test parameters loosely based on Keesler site. Some adaptations to allow for more robust tests. + @pytest.fixture(scope="session") def test_hydro_pars(): """HydrologicalParameters fixture with example data for tests.""" - return HydrologicalParameters(velocity=10 / 365, porosity=0.25, alpha_x=10, alpha_y=1, alpha_z=0.1) + return HydrologicalParameters( + h_gradient=0.048, # [m/m] + h_conductivity=0.495, # [m/d] + porosity=0.25, # [-] + alpha_x=4.1, # [m] + alpha_y=0.4, # [m] + alpha_z=0.01, # [m] + ) @pytest.fixture(scope="session") def test_att_pars(): """AttenuationParameters fixture with example data for tests.""" return AttenuationParameters( - retardation=1, - half_life=0.1 * 365, + bulk_density=1.7, # [kg/L] + partition_coefficient=38, # [L/kg] + fraction_organic_carbon=0.000057, # [-] + half_life=0.5 * 365, # [1/day] ) @@ -29,36 +40,51 @@ def test_att_pars(): def test_att_pars_nodecay(): """AttenuationParameters fixture with example data for tests.""" return AttenuationParameters( - retardation=1, - decay_rate=0, + bulk_density=1.7, # [kg/L] + partition_coefficient=38, # [L/kg] + fraction_organic_carbon=0.000057, # [-] + decay_rate=0, # [-] ) -electron_acceptor_dict = dict(delta_oxygen=0.5, delta_nitrate=0.5, ferrous_iron=0.5, delta_sulfate=0.5, methane=0.5) +electron_acceptor_dict = dict( + delta_oxygen=2.05 - 0.4, # [g/m3] + delta_nitrate=0.07 - 0, # [g/m3] + ferrous_iron=16.6, # [g/m3] + delta_sulfate=26.2 - 3.8, # [g/m3] + methane=6.6, # [g/m3] +) @pytest.fixture(scope="session") def test_source_pars(): """SourceParameters fixture with example data for tests.""" return SourceParameters( - source_zone_boundary=np.array([5, 10, 15]), - source_zone_concentration=np.array([10, 5, 2]), - depth=10, - total_mass=1000000, + source_zone_boundary=np.array([2, 11, 20]), # [m] + source_zone_concentration=np.array([13.68, 2.508, 0.057]), # [g/m3] + depth=3, # [m] + total_mass=2000000, # [g] ) @pytest.fixture(scope="session") def test_model_pars(): """ModelParameters fixture with example data for tests.""" - return ModelParameters(model_length=50, model_width=30, model_time=3 * 365, dx=10, dy=5, dt=1 * 365) + return ModelParameters( + model_length=100, # [m] + model_width=40, # [m] + model_time=5 * 365, # [days] + dx=20, # [m] + dy=10, # [m] + dt=365, # [days] + ) @pytest.fixture(scope="session") def test_model_pars_short(test_source_pars, test_model_pars): """Model Parameters fixture with smaller model width for testing.""" short_model_pars = copy.copy(test_model_pars) - short_model_pars.model_width = test_source_pars.source_zone_boundary[-1] - 1 + short_model_pars.model_width = test_source_pars.source_zone_boundary[-1] return short_model_pars diff --git a/tests/test_anatrans.py b/tests/test_anatrans.py index 0bf82e4..bce131c 100644 --- a/tests/test_anatrans.py +++ b/tests/test_anatrans.py @@ -24,8 +24,8 @@ def test_transport_equation_numerical_anatrans(model, expected, request): @pytest.mark.parametrize( "x, y, t, expected", [ - (16, 0, 393, 0.2403406438598838), - (24, -5, 283, 0.031529981399875194), + (16, 0, 393, 4.041372051306399), + (24, -5, 283, 1.5760137786262713), (-16, 0, 393, DomainValueError), ("nonsense", 0, 393, TypeError), (16, "nonsense", 393, TypeError), @@ -46,8 +46,8 @@ def test_anatrans_sample_linear(x, y, t, expected, test_anatrans_model_lineardec @pytest.mark.parametrize( "x, y, t, expected", [ - (20, 0, 476, 3.076798202181921), - (11, 7, 193, 2.0747279062256183), + (20, 0, 476, 5.540354132380653), + (54, 3, 1045, 3.501165953555688), (-16, 0, 393, DomainValueError), ("nonsense", 0, 393, TypeError), (16, "nonsense", 393, TypeError), diff --git a/tests/test_bioscreen.py b/tests/test_bioscreen.py index eb1aa22..553aeab 100644 --- a/tests/test_bioscreen.py +++ b/tests/test_bioscreen.py @@ -24,8 +24,8 @@ def test_transport_equation_numerical_bioscreen(model, expected, request): @pytest.mark.parametrize( "x, y, t, expected", [ - (9, 0, 629, 1.2260728205395477), - (15, -7, 256, 0.21033402922523056), + (9, 0, 629, 6.222919410416837), + (15, -7, 256, 1.5292214426149926), (-16, 0, 393, DomainValueError), ("nonsense", 0, 393, TypeError), (16, "nonsense", 393, TypeError), @@ -46,8 +46,8 @@ def test_bioscreen_sample_linear(x, y, t, expected, test_bioscreen_model_lineard @pytest.mark.parametrize( "x, y, t, expected", [ - (13, 0, 354, 2.721953070355462), - (11, 3, 752, 5.01432266465888), + (13, 0, 354, 5.134230309076454), + (11, 3, 752, 5.645872356690198), (-16, 0, 393, DomainValueError), ("nonsense", 0, 393, TypeError), (16, "nonsense", 393, TypeError), diff --git a/tests/test_example_data.py b/tests/test_example_data.py index ad13c73..9780977 100644 --- a/tests/test_example_data.py +++ b/tests/test_example_data.py @@ -8,31 +8,39 @@ testingdata_nodecay_anatrans = np.array( [ [ - [0.0, 1.04119211, 0.70235024, 0.28178549, 0.06874379, 0.01026263], - [1.99151804, 2.77902449, 1.49534136, 0.51880451, 0.11462681, 0.01593092], - [4.97879509, 5.0070735, 2.34665292, 0.7467676, 0.15558745, 0.02072489], - [9.95759018, 6.1418951, 2.72894706, 0.84307586, 0.17224172, 0.02262182], - [4.97879509, 5.0070735, 2.34665292, 0.7467676, 0.15558745, 0.02072489], - [1.99151804, 2.77902449, 1.49534136, 0.51880451, 0.11462681, 0.01593092], - [0.0, 1.04119211, 0.70235024, 0.28178549, 0.06874379, 0.01026263], - ], - [ - [0.0, 1.26811687, 1.28567955, 0.97182081, 0.56931262, 0.2614138], - [1.98307205, 3.38470473, 2.73728078, 1.78925118, 0.94930018, 0.40579866], - [4.95768011, 6.0983505, 4.29563986, 2.57544947, 1.28852225, 0.52791258], - [9.91536023, 7.48050315, 4.99544422, 2.90759705, 1.4264472, 0.57623172], - [4.95768011, 6.0983505, 4.29563986, 2.57544947, 1.28852225, 0.52791258], - [1.98307205, 3.38470473, 2.73728078, 1.78925118, 0.94930018, 0.40579866], - [0.0, 1.26811687, 1.28567955, 0.97182081, 0.56931262, 0.2614138], - ], - [ - [0.0, 1.34821759, 1.55360571, 1.44377422, 1.12223112, 0.74311474], - [1.97466187, 3.59849991, 3.30770995, 2.65818009, 1.87126399, 1.15355412], - [4.93665468, 6.48355336, 5.19081959, 3.82618639, 2.53993977, 1.50068444], - [9.87330937, 7.95300981, 6.03645803, 4.31963755, 2.81181794, 1.6380401], - [4.93665468, 6.48355336, 5.19081959, 3.82618639, 2.53993977, 1.50068444], - [1.97466187, 3.59849991, 3.30770995, 2.65818009, 1.87126399, 1.15355412], - [0.0, 1.34821759, 1.55360571, 1.44377422, 1.12223112, 0.74311474], + [0.00000000e00, 5.15094278e-02, 7.68809285e-02, 2.54392936e-02, 1.97695247e-03, 3.59680678e-05], + [2.50471057e00, 1.55246828e00, 9.52222337e-01, 1.91704880e-01, 1.05811778e-02, 1.50150442e-04], + [1.36620576e01, 5.96227228e00, 2.43155983e00, 3.91260068e-01, 1.88411715e-02, 2.43895960e-04], + [2.50471057e00, 1.55246828e00, 9.52222337e-01, 1.91704880e-01, 1.05811778e-02, 1.50150442e-04], + [0.00000000e00, 5.15094278e-02, 7.68809285e-02, 2.54392936e-02, 1.97695247e-03, 3.59680678e-05], + ], + [ + [0.00000000e00, 5.79192855e-02, 1.59653540e-01, 2.17198355e-01, 1.62412180e-01, 6.37391410e-02], + [2.50142545e00, 1.74565817e00, 1.97741716e00, 1.63675868e00, 8.69273378e-01, 2.66082133e-01], + [1.36441388e01, 6.70422027e00, 5.04945951e00, 3.34054257e00, 1.54785499e00, 4.32208899e-01], + [2.50142545e00, 1.74565817e00, 1.97741716e00, 1.63675868e00, 8.69273378e-01, 2.66082133e-01], + [0.00000000e00, 5.79192855e-02, 1.59653540e-01, 2.17198355e-01, 1.62412180e-01, 6.37391410e-02], + ], + [ + [0.00000000e00, 5.82768590e-02, 1.70977009e-01, 2.93071143e-01, 3.66114067e-01, 3.32049611e-01], + [2.49814463e00, 1.75643526e00, 2.11766599e00, 2.20851920e00, 1.95954030e00, 1.38615719e00], + [1.36262435e01, 6.74560979e00, 5.40759375e00, 4.50747718e00, 3.48921789e00, 2.25159603e00], + [2.49814463e00, 1.75643526e00, 2.11766599e00, 2.20851920e00, 1.95954030e00, 1.38615719e00], + [0.00000000e00, 5.82768590e-02, 1.70977009e-01, 2.93071143e-01, 3.66114067e-01, 3.32049611e-01], + ], + [ + [0.00000000e00, 5.82348597e-02, 1.71971516e-01, 3.05437286e-01, 4.28425559e-01, 5.01951093e-01], + [2.49486813e00, 1.75516942e00, 2.12998362e00, 2.30170771e00, 2.29304806e00, 2.09541916e00], + [1.36083716e01, 6.74074833e00, 5.43904760e00, 4.69767027e00, 4.08307209e00, 3.40368141e00], + [2.49486813e00, 1.75516942e00, 2.12998362e00, 2.30170771e00, 2.29304806e00, 2.09541916e00], + [0.00000000e00, 5.82348597e-02, 1.71971516e-01, 3.05437286e-01, 4.28425559e-01, 5.01951093e-01], + ], + [ + [0.00000000e00, 5.81615084e-02, 1.71870672e-01, 3.06710818e-01, 4.39468968e-01, 5.49534752e-01], + [2.49159591e00, 1.75295865e00, 2.12873460e00, 2.31130477e00, 2.35215533e00, 2.29405945e00], + [1.35905232e01, 6.73225784e00, 5.43585816e00, 4.71725738e00, 4.18832032e00, 3.72634156e00], + [2.49159591e00, 1.75295865e00, 2.12873460e00, 2.31130477e00, 2.35215533e00, 2.29405945e00], + [0.00000000e00, 5.81615084e-02, 1.71870672e-01, 3.06710818e-01, 4.39468968e-01, 5.49534752e-01], ], ] ) @@ -40,31 +48,39 @@ testingdata_lineardecay_anatrans = np.array( [ [ - [0.00000000e00, 1.64871457e-01, 2.44919161e-02, 3.14034072e-03, 3.47905205e-04, 3.10817940e-05], - [1.99151804e00, 4.40055023e-01, 5.21446040e-02, 5.78178433e-03, 5.80114442e-04, 4.82489846e-05], - [4.97879509e00, 7.92863774e-01, 8.18310059e-02, 8.32230465e-03, 7.87412011e-04, 6.27681864e-05], - [9.95759018e00, 9.72561344e-01, 9.51621267e-02, 9.39560597e-03, 8.71697529e-04, 6.85132756e-05], - [4.97879509e00, 7.92863774e-01, 8.18310059e-02, 8.32230465e-03, 7.87412011e-04, 6.27681864e-05], - [1.99151804e00, 4.40055023e-01, 5.21446040e-02, 5.78178433e-03, 5.80114442e-04, 4.82489846e-05], - [0.00000000e00, 1.64871457e-01, 2.44919161e-02, 3.14034072e-03, 3.47905205e-04, 3.10817940e-05], + [0.00000000e00, 2.76361560e-02, 2.81216130e-02, 7.77848501e-03, 5.56690595e-04, 9.71508537e-06], + [2.50471057e00, 8.32939862e-01, 3.48305211e-01, 5.86169396e-02, 2.97955679e-03, 4.05560947e-05], + [1.36620576e01, 3.19891511e00, 8.89419338e-01, 1.19634241e-01, 5.30549072e-03, 6.58770465e-05], + [2.50471057e00, 8.32939862e-01, 3.48305211e-01, 5.86169396e-02, 2.97955679e-03, 4.05560947e-05], + [0.00000000e00, 2.76361560e-02, 2.81216130e-02, 7.77848501e-03, 5.56690595e-04, 9.71508537e-06], + ], + [ + [0.00000000e00, 2.87351067e-02, 4.13485527e-02, 3.36274105e-02, 1.79177815e-02, 5.75408718e-03], + [2.50142545e00, 8.66061682e-01, 5.12129812e-01, 2.53408715e-01, 9.59007536e-02, 2.40207158e-02], + [1.36441388e01, 3.32611984e00, 1.30775579e00, 5.17194508e-01, 1.70763840e-01, 3.90179040e-02], + [2.50142545e00, 8.66061682e-01, 5.12129812e-01, 2.53408715e-01, 9.59007536e-02, 2.40207158e-02], + [0.00000000e00, 2.87351067e-02, 4.13485527e-02, 3.36274105e-02, 1.79177815e-02, 5.75408718e-03], + ], + [ + [0.00000000e00, 2.87161384e-02, 4.17780652e-02, 3.66139095e-02, 2.53757095e-02, 1.45537551e-02], + [2.49814463e00, 8.65489988e-01, 5.17449615e-01, 2.75914309e-01, 1.35817577e-01, 6.07553559e-02], + [1.36262435e01, 3.32392424e00, 1.32134025e00, 5.63127300e-01, 2.41840966e-01, 9.86875939e-02], + [2.49814463e00, 8.65489988e-01, 5.17449615e-01, 2.75914309e-01, 1.35817577e-01, 6.07553559e-02], + [0.00000000e00, 2.87161384e-02, 4.17780652e-02, 3.66139095e-02, 2.53757095e-02, 1.45537551e-02], ], [ - [0.00000000e00, 1.64220271e-01, 2.45988950e-02, 3.24164212e-03, 4.00276470e-04, 4.75980035e-05], - [1.98307205e00, 4.38316957e-01, 5.23723679e-02, 5.96829367e-03, 6.67440892e-04, 7.38874769e-05], - [4.95768011e00, 7.89732236e-01, 8.21884379e-02, 8.59076634e-03, 9.05943616e-04, 9.61218762e-05], - [9.91536023e00, 9.68720062e-01, 9.55777881e-02, 9.69869031e-03, 1.00291690e-03, 1.04919784e-04], - [4.95768011e00, 7.89732236e-01, 8.21884379e-02, 8.59076634e-03, 9.05943616e-04, 9.61218762e-05], - [1.98307205e00, 4.38316957e-01, 5.23723679e-02, 5.96829367e-03, 6.67440892e-04, 7.38874769e-05], - [0.00000000e00, 1.64220271e-01, 2.45988950e-02, 3.24164212e-03, 4.00276470e-04, 4.75980035e-05], + [0.00000000e00, 2.86788433e-02, 4.17361209e-02, 3.66969967e-02, 2.59643206e-02, 1.61364696e-02], + [2.49486813e00, 8.64365933e-01, 5.16930107e-01, 2.76540436e-01, 1.38967980e-01, 6.73624743e-02], + [1.36083716e01, 3.31960730e00, 1.32001365e00, 5.64405193e-01, 2.47450672e-01, 1.09419827e-01], + [2.49486813e00, 8.64365933e-01, 5.16930107e-01, 2.76540436e-01, 1.38967980e-01, 6.73624743e-02], + [0.00000000e00, 2.86788433e-02, 4.17361209e-02, 3.66969967e-02, 2.59643206e-02, 1.61364696e-02], ], [ - [0.00000000e00, 1.63523832e-01, 2.44946191e-02, 3.24171853e-03, 4.00357148e-04, 4.76590838e-05], - [1.97466187e00, 4.36458104e-01, 5.21503589e-02, 5.96843435e-03, 6.67575420e-04, 7.39822933e-05], - [4.93665468e00, 7.86383070e-01, 8.18400370e-02, 8.59096883e-03, 9.06126216e-04, 9.62452250e-05], - [9.87330937e00, 9.64611829e-01, 9.51726292e-02, 9.69891892e-03, 1.00311904e-03, 1.05054423e-04], - [4.93665468e00, 7.86383070e-01, 8.18400370e-02, 8.59096883e-03, 9.06126216e-04, 9.62452250e-05], - [1.97466187e00, 4.36458104e-01, 5.21503589e-02, 5.96843435e-03, 6.67575420e-04, 7.39822933e-05], - [0.00000000e00, 1.63523832e-01, 2.44946191e-02, 3.24171853e-03, 4.00357148e-04, 4.76590838e-05], + [0.00000000e00, 2.86412369e-02, 4.16817095e-02, 3.66532141e-02, 2.59597664e-02, 1.62344017e-02], + [2.49159591e00, 8.63232494e-01, 5.16256185e-01, 2.76210501e-01, 1.38943605e-01, 6.77712966e-02], + [1.35905232e01, 3.31525431e00, 1.31829275e00, 5.63731811e-01, 2.47407269e-01, 1.10083895e-01], + [2.49159591e00, 8.63232494e-01, 5.16256185e-01, 2.76210501e-01, 1.38943605e-01, 6.77712966e-02], + [0.00000000e00, 2.86412369e-02, 4.16817095e-02, 3.66532141e-02, 2.59597664e-02, 1.62344017e-02], ], ] ) @@ -72,31 +88,39 @@ testingdata_instantreaction_anatrans = np.array( [ [ - [0.0, 0.37775248, 0.0, 0.0, 0.0, 0.0], - [1.98480819, 2.38673103, 0.75942263, 0.0, 0.0, 0.0], - [4.96977484, 4.70247987, 1.66982103, 0.0, 0.0, 0.0], - [9.94471925, 5.84605042, 2.06717574, 0.0, 0.0, 0.0], - [4.96977484, 4.70247987, 1.66982103, 0.0, 0.0, 0.0], - [1.98480819, 2.38673103, 0.75942263, 0.0, 0.0, 0.0], - [0.0, 0.37775248, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [2.35751237, 0.0, 0.0, 0.0, 0.0, 0.0], + [13.43082864, 4.15251038, 0.0, 0.0, 0.0, 0.0], + [2.35751237, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + ], + [ + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [2.20835401, 1.33975922, 0.24351093, 0.0, 0.0, 0.0], + [13.18385824, 6.33339313, 3.80213221, 0.0, 0.0, 0.0], + [2.20835401, 1.33975922, 0.24351093, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], ], [ - [0.0, 0.68359367, 0.59867174, 0.16364701, 0.0, 0.0], - [1.96969251, 3.12853079, 2.24695425, 1.08840019, 0.08763987, 0.0], - [4.93970113, 5.94680893, 3.9134741, 1.94461756, 0.46189714, 0.0], - [9.88971551, 7.3385401, 4.64084741, 2.29931647, 0.61202226, 0.0], - [4.93970113, 5.94680893, 3.9134741, 1.94461756, 0.46189714, 0.0], - [1.96969251, 3.12853079, 2.24695425, 1.08840019, 0.08763987, 0.0], - [0.0, 0.68359367, 0.59867174, 0.16364701, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [2.06051319, 1.31903162, 1.18223909, 0.23641044, 0.0, 0.0], + [12.93906936, 6.30614278, 4.96699928, 3.47149581, 0.50349427, 0.0], + [2.06051319, 1.31903162, 1.18223909, 0.23641044, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], ], [ - [0.0, 0.79052499, 0.93688906, 0.74411488, 0.32657928, 0.0], - [1.95465257, 3.38788638, 2.92712105, 2.11796363, 1.17467, 0.3252468], - [4.90977814, 6.38186393, 4.93937386, 3.38999296, 1.91240731, 0.71193944], - [9.83498741, 7.86035955, 5.81764661, 3.91694724, 2.20833448, 0.86347462], - [4.90977814, 6.38186393, 4.93937386, 3.38999296, 1.91240731, 0.71193944], - [1.95465257, 3.38788638, 2.92712105, 2.11796363, 1.17467, 0.3252468], - [0.0, 0.79052499, 0.93688906, 0.74411488, 0.32657928, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [1.91397827, 1.18853384, 1.15644927, 0.75166046, 0.0, 0.0], + [12.69644273, 6.13457882, 4.93499671, 4.09963864, 2.95459379, 0.93646409], + [1.91397827, 1.18853384, 1.15644927, 0.75166046, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + ], + [ + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [1.76873771, 1.05053556, 1.02946157, 0.70145777, 0.18347740, 0.0], + [12.45595925, 5.95315404, 4.77741777, 4.03843640, 3.27764482, 2.29352416], + [1.76873771, 1.05053556, 1.02946157, 0.70145777, 0.18347740, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], ], ] ) @@ -104,31 +128,39 @@ testingdata_nodecay_mibitrans = np.array( [ [ - [1.99151804e00, 8.01554517e-01, 4.52622845e-01, 1.64844210e-01, 3.80093542e-02, 5.49309885e-03], - [4.97879509e00, 2.56998867e00, 1.35186278e00, 4.72834457e-01, 1.06531042e-01, 1.51816855e-02], - [9.95759018e00, 5.22746226e00, 2.62549664e00, 8.93532734e-01, 1.98188547e-01, 2.79800829e-02], - [9.95759018e00, 6.80026270e00, 3.33844274e00, 1.12085955e00, 2.46720295e-01, 3.46763177e-02], - [9.95759018e00, 5.22746226e00, 2.62549664e00, 8.93532734e-01, 1.98188547e-01, 2.79800829e-02], - [4.97879509e00, 2.56998867e00, 1.35186278e00, 4.72834457e-01, 1.06531042e-01, 1.51816855e-02], - [1.99151804e00, 8.01554517e-01, 4.52622845e-01, 1.64844210e-01, 3.80093542e-02, 5.49309885e-03], + [5.69252401e-02, 4.58655625e-02, 3.90255339e-02, 8.96862490e-03, 5.32129779e-04, 7.98682366e-06], + [2.50471057e00, 1.53375416e00, 8.36005251e-01, 1.61715039e-01, 8.91028965e-03, 1.28892936e-04], + [1.36620576e01, 6.59641390e00, 2.83179036e00, 4.98247612e-01, 2.63789498e-02, 3.74275771e-04], + [2.50471057e00, 1.53375416e00, 8.36005251e-01, 1.61715039e-01, 8.91028965e-03, 1.28892936e-04], + [5.69252401e-02, 4.58655625e-02, 3.90255339e-02, 8.96862490e-03, 5.32129779e-04, 7.98682366e-06], + ], + [ + [5.68505783e-02, 6.81042656e-02, 1.43636130e-01, 1.63249250e-01, 1.02952664e-01, 3.51019523e-02], + [2.50142545e00, 1.77279686e00, 1.89160110e00, 1.55821397e00, 8.37626901e-01, 2.62585163e-01], + [1.36441388e01, 7.17629015e00, 5.33202716e00, 3.64352788e00, 1.79202159e00, 5.37857929e-01], + [2.50142545e00, 1.77279686e00, 1.89160110e00, 1.55821397e00, 8.37626901e-01, 2.62585163e-01], + [5.68505783e-02, 6.81042656e-02, 1.43636130e-01, 1.63249250e-01, 1.02952664e-01, 3.51019523e-02], + ], + [ + [5.67760144e-02, 7.12301289e-02, 1.72756124e-01, 2.73763611e-01, 3.15357372e-01, 2.63812763e-01], + [2.49814463e00, 1.78799606e00, 2.04698456e00, 2.14036310e00, 1.92467181e00, 1.38999780e00], + [1.36262435e01, 7.19843992e00, 5.60813338e00, 4.67845591e00, 3.70468280e00, 2.49346106e00], + [2.49814463e00, 1.78799606e00, 2.04698456e00, 2.14036310e00, 1.92467181e00, 1.38999780e00], + [5.67760144e-02, 7.12301289e-02, 1.72756124e-01, 2.73763611e-01, 3.15357372e-01, 2.63812763e-01], ], [ - [1.98307205e00, 1.05863751e00, 9.50641745e-01, 6.78487445e-01, 3.85488198e-01, 1.74322606e-01], - [4.95768011e00, 3.18639308e00, 2.54178563e00, 1.68716469e00, 9.16307826e-01, 4.02464559e-01], - [9.91536023e00, 6.26828198e00, 4.63189722e00, 2.92801637e00, 1.54285778e00, 6.64735162e-01], - [9.91536023e00, 8.04415259e00, 5.73595388e00, 3.54603967e00, 1.84410777e00, 7.88202589e-01], - [9.91536023e00, 6.26828198e00, 4.63189722e00, 2.92801637e00, 1.54285778e00, 6.64735162e-01], - [4.95768011e00, 3.18639308e00, 2.54178563e00, 1.68716469e00, 9.16307826e-01, 4.02464559e-01], - [1.98307205e00, 1.05863751e00, 9.50641745e-01, 6.78487445e-01, 3.85488198e-01, 1.74322606e-01], + [5.67015483e-02, 7.15062607e-02, 1.76971815e-01, 2.99582537e-01, 4.05306818e-01, 4.58477235e-01], + [2.49486813e00, 1.78700991e00, 2.06057741e00, 2.23289583e00, 2.24833573e00, 2.08380398e00], + [1.36083716e01, 7.19111142e00, 5.62605483e00, 4.82006101e00, 4.20374834e00, 3.56044346e00], + [2.49486813e00, 1.78700991e00, 2.06057741e00, 2.23289583e00, 2.24833573e00, 2.08380398e00], + [5.67015483e-02, 7.15062607e-02, 1.76971815e-01, 2.99582537e-01, 4.05306818e-01, 4.58477235e-01], ], [ - [1.97466187e00, 1.17386221e00, 1.23604339e00, 1.10357596e00, 8.44494958e-01, 5.56449310e-01], - [4.93665468e00, 3.41083262e00, 3.10544033e00, 2.52726539e00, 1.82120160e00, 1.15279266e00], - [9.87330937e00, 6.60043957e00, 5.47750648e00, 4.18989775e00, 2.90031733e00, 1.78770040e00], - [9.87330937e00, 8.42153416e00, 6.70381927e00, 4.99150381e00, 3.39849046e00, 2.07306879e00], - [9.87330937e00, 6.60043957e00, 5.47750648e00, 4.18989775e00, 2.90031733e00, 1.78770040e00], - [4.93665468e00, 3.41083262e00, 3.10544033e00, 2.52726539e00, 1.82120160e00, 1.15279266e00], - [1.97466187e00, 1.17386221e00, 1.23604339e00, 1.10357596e00, 8.44494958e-01, 5.56449310e-01], + [5.66271799e-02, 7.14518018e-02, 1.77288676e-01, 3.03326188e-01, 4.24772481e-01, 5.23557841e-01], + [2.49159591e00, 1.78477885e00, 2.05944649e00, 2.24178193e00, 2.30230626e00, 2.26717215e00], + [1.35905232e01, 7.18184037e00, 5.62091401e00, 4.83055062e00, 4.27913354e00, 3.81986900e00], + [2.49159591e00, 1.78477885e00, 2.05944649e00, 2.24178193e00, 2.30230626e00, 2.26717215e00], + [5.66271799e-02, 7.14518018e-02, 1.77288676e-01, 3.03326188e-01, 4.24772481e-01, 5.23557841e-01], ], ] ) @@ -136,31 +168,39 @@ testingdata_lineardecay_mibitrans = np.array( [ [ - [1.99151804e00, 1.15460753e-01, 1.39779749e-02, 1.66729845e-03, 1.80447573e-04, 1.60040845e-05], - [4.97879509e00, 3.96569754e-01, 4.55738667e-02, 5.13035685e-03, 5.29922012e-04, 4.55133917e-05], - [9.95759018e00, 8.40429884e-01, 9.35602124e-02, 1.01496717e-02, 1.01669074e-03, 8.54776276e-05], - [9.95759018e00, 1.11467694e00, 1.22196524e-01, 1.30175277e-02, 1.28447353e-03, 1.06880942e-04], - [9.95759018e00, 8.40429884e-01, 9.35602124e-02, 1.01496717e-02, 1.01669074e-03, 8.54776276e-05], - [4.97879509e00, 3.96569754e-01, 4.55738667e-02, 5.13035685e-03, 5.29922012e-04, 4.55133917e-05], - [1.99151804e00, 1.15460753e-01, 1.39779749e-02, 1.66729845e-03, 1.80447573e-04, 1.60040845e-05], + [5.69252401e-02, 2.13852839e-02, 1.32216153e-02, 2.66226452e-03, 1.48031634e-04, 2.14520312e-06], + [2.50471057e00, 8.11792437e-01, 3.01596391e-01, 4.91567828e-02, 2.50295905e-03, 3.47757905e-05], + [1.36620576e01, 3.68067444e00, 1.05444133e00, 1.53363080e-01, 7.44785137e-03, 1.01215029e-04], + [2.50471057e00, 8.11792437e-01, 3.01596391e-01, 4.91567828e-02, 2.50295905e-03, 3.47757905e-05], + [5.69252401e-02, 2.13852839e-02, 1.32216153e-02, 2.66226452e-03, 1.48031634e-04, 2.14520312e-06], ], [ - [1.98307205e00, 1.15020755e-01, 1.40024094e-02, 1.72856547e-03, 2.12281807e-04, 2.58220087e-05], - [4.95768011e00, 3.95016164e-01, 4.55963116e-02, 5.28401874e-03, 6.10824823e-04, 7.02847995e-05], - [9.91536023e00, 8.37092113e-01, 9.35438840e-02, 1.04152947e-02, 1.15813681e-03, 1.28616723e-04], - [9.91536023e00, 1.11022513e00, 1.22140889e-01, 1.33372852e-02, 1.45582545e-03, 1.59070200e-04], - [9.91536023e00, 8.37092113e-01, 9.35438840e-02, 1.04152947e-02, 1.15813681e-03, 1.28616723e-04], - [4.95768011e00, 3.95016164e-01, 4.55963116e-02, 5.28401874e-03, 6.10824823e-04, 7.02847995e-05], - [1.98307205e00, 1.15020755e-01, 1.40024094e-02, 1.72856547e-03, 2.12281807e-04, 2.58220087e-05], + [5.68505783e-02, 2.49566391e-02, 2.84481264e-02, 2.15344163e-02, 1.04480147e-02, 3.03744263e-03], + [2.50142545e00, 8.52488603e-01, 4.68088838e-01, 2.34825540e-01, 9.12767077e-02, 2.35779114e-02], + [1.36441388e01, 3.78077423e00, 1.46227339e00, 5.87628221e-01, 2.02435702e-01, 4.91720644e-02], + [2.50142545e00, 8.52488603e-01, 4.68088838e-01, 2.34825540e-01, 9.12767077e-02, 2.35779114e-02], + [5.68505783e-02, 2.49566391e-02, 2.84481264e-02, 2.15344163e-02, 1.04480147e-02, 3.03744263e-03], ], [ - [1.97466187e00, 1.14532975e-01, 1.39430733e-02, 1.72130045e-03, 2.11445188e-04, 2.57583457e-05], - [4.93665468e00, 3.93340951e-01, 4.54030381e-02, 5.26174621e-03, 6.08366660e-04, 7.00818864e-05], - [9.87330937e00, 8.33542087e-01, 9.31473205e-02, 1.03713356e-02, 1.15342995e-03, 1.28218392e-04], - [9.87330937e00, 1.10551676e00, 1.21623071e-01, 1.32809674e-02, 1.44988830e-03, 1.58565805e-04], - [9.87330937e00, 8.33542087e-01, 9.31473205e-02, 1.03713356e-02, 1.15342995e-03, 1.28218392e-04], - [4.93665468e00, 3.93340951e-01, 4.54030381e-02, 5.26174621e-03, 6.08366660e-04, 7.00818864e-05], - [1.97466187e00, 1.14532975e-01, 1.39430733e-02, 1.72130045e-03, 2.11445188e-04, 2.57583457e-05], + [5.67760144e-02, 2.50576742e-02, 2.95923313e-02, 2.57252568e-02, 1.78738782e-02, 1.01891729e-02], + [2.49814463e00, 8.52127129e-01, 4.74096396e-01, 2.57767602e-01, 1.31093707e-01, 6.05779265e-02], + [1.36262435e01, 3.77719624e00, 1.47239927e00, 6.28891841e-01, 2.73688795e-01, 1.14529271e-01], + [2.49814463e00, 8.52127129e-01, 4.74096396e-01, 2.57767602e-01, 1.31093707e-01, 6.05779265e-02], + [5.67760144e-02, 2.50576742e-02, 2.95923313e-02, 2.57252568e-02, 1.78738782e-02, 1.01891729e-02], + ], + [ + [5.67015483e-02, 2.50286883e-02, 2.95994517e-02, 2.59551922e-02, 1.87258624e-02, 1.19669029e-02], + [2.49486813e00, 8.51024089e-01, 4.73646879e-01, 2.58413920e-01, 1.34166926e-01, 6.70723478e-02], + [1.36083716e01, 3.77226501e00, 1.47073779e00, 6.29605339e-01, 2.78388880e-01, 1.24592779e-01], + [2.49486813e00, 8.51024089e-01, 4.73646879e-01, 2.58413920e-01, 1.34166926e-01, 6.70723478e-02], + [5.67015483e-02, 2.50286883e-02, 2.95994517e-02, 2.59551922e-02, 1.87258624e-02, 1.19669029e-02], + ], + [ + [5.66271799e-02, 2.49959648e-02, 2.95620621e-02, 2.59317814e-02, 1.87515748e-02, 1.21114805e-02], + [2.49159591e00, 8.49908207e-01, 4.73029820e-01, 2.58105843e-01, 1.34136464e-01, 6.74465909e-02], + [1.35905232e01, 3.76731783e00, 1.46881476e00, 6.28823699e-01, 2.78231721e-01, 1.25089190e-01], + [2.49159591e00, 8.49908207e-01, 4.73029820e-01, 2.58105843e-01, 1.34136464e-01, 6.74465909e-02], + [5.66271799e-02, 2.49959648e-02, 2.95620621e-02, 2.59317814e-02, 1.87515748e-02, 1.21114805e-02], ], ] ) @@ -168,31 +208,39 @@ testingdata_instantreaction_mibitrans = np.array( [ [ - [1.98480819, 0.13464753, 0.0, 0.0, 0.0, 0.0], - [4.96977484, 2.23701414, 0.66335351, 0.0, 0.0, 0.0], - [9.94471925, 4.92028989, 1.96263113, 0.0, 0.0, 0.0], - [9.94471925, 6.49249355, 2.67604441, 0.21615243, 0.0, 0.0], - [9.94471925, 4.92028989, 1.96263113, 0.0, 0.0, 0.0], - [4.96977484, 2.23701414, 0.66335351, 0.0, 0.0, 0.0], - [1.98480819, 0.13464753, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [2.35751237, 0.0, 0.0, 0.0, 0.0, 0.0], + [13.43082864, 4.78241455, 0.0, 0.0, 0.0, 0.0], + [2.35751237, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], ], [ - [1.96969251, 0.46935686, 0.25623422, 0.0, 0.0, 0.0], - [4.93970113, 2.98031895, 2.10160863, 1.0233045, 0.07694492, 0.0], - [9.88971551, 6.10733056, 4.25752951, 2.31745682, 0.73489692, 0.0], - [9.88971551, 7.8847924, 5.36824061, 2.94301855, 1.04148795, 0.0], - [9.88971551, 6.10733056, 4.25752951, 2.31745682, 0.73489692, 0.0], - [4.93970113, 2.98031895, 2.10160863, 1.0233045, 0.07694492, 0.0], - [1.96969251, 0.46935686, 0.25623422, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [2.20835401, 1.31154004, 0.22305599, 0.0, 0.0, 0.0], + [13.18385824, 6.79238283, 4.06824236, 0.0, 0.0, 0.0], + [2.20835401, 1.31154004, 0.22305599, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + ], + [ + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [2.06051319, 1.28341184, 1.09837108, 0.24624916, 0.0, 0.0], + [12.93906936, 6.73942235, 5.12143805, 3.60761611, 0.79563498, 0.0], + [2.06051319, 1.28341184, 1.09837108, 0.24624916, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], ], [ - [1.95465257, 0.61070925, 0.60959608, 0.39221492, 0.0400149, 0.0], - [4.90977814, 3.24249625, 2.76727224, 2.01777733, 1.14882233, 0.34221937], - [9.83498741, 6.48351156, 5.22465662, 3.76411963, 2.29262399, 1.01924874], - [9.83498741, 8.30731613, 6.46277784, 4.58169461, 2.80553108, 1.31527806], - [9.83498741, 6.48351156, 5.22465662, 3.76411963, 2.29262399, 1.01924874], - [4.90977814, 3.24249625, 2.76727224, 2.01777733, 1.14882233, 0.34221937], - [1.95465257, 0.61070925, 0.60959608, 0.39221492, 0.0400149, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [1.91397827, 1.15201201, 1.05938521, 0.69569679, 0.0, 0.0], + [12.69644273, 6.56148289, 5.06671121, 4.14314700, 3.03878990, 1.17789561], + [1.91397827, 1.15201201, 1.05938521, 0.69569679, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + ], + [ + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [1.76873771, 1.01417715, 0.93110472, 0.63196474, 0.15972572, 0.0], + [12.45595925, 6.37599267, 4.90480591, 4.06227650, 3.28852099, 2.36272490], + [1.76873771, 1.01417715, 0.93110472, 0.63196474, 0.15972572, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], ], ] ) @@ -200,31 +248,39 @@ testingdata_nodecay_bioscreen = np.array( [ [ - [0.00000000e00, 7.29338809e-01, 4.61369298e-01, 1.76413628e-01, 4.15239315e-02, 6.03372787e-03], - [1.51405147e00, 1.94666325e00, 9.82280001e-01, 3.24800918e-01, 6.92390684e-02, 9.36629471e-03], - [3.78512866e00, 3.50737679e00, 1.54150103e00, 4.67518683e-01, 9.39808944e-02, 1.21848229e-02], - [7.57025733e00, 4.30230160e00, 1.79262755e00, 5.27813089e-01, 1.04040721e-01, 1.33000836e-02], - [3.78512866e00, 3.50737679e00, 1.54150103e00, 4.67518683e-01, 9.39808944e-02, 1.21848229e-02], - [1.51405147e00, 1.94666325e00, 9.82280001e-01, 3.24800918e-01, 6.92390684e-02, 9.36629471e-03], - [0.00000000e00, 7.29338809e-01, 4.61369298e-01, 1.76413628e-01, 4.15239315e-02, 6.03372787e-03], + [0.00000000e00, 4.68668935e-02, 6.29165282e-02, 1.89769059e-02, 1.37627350e-03, 2.38011764e-05], + [2.45315202e00, 1.41254463e00, 7.79263787e-01, 1.43005758e-01, 7.36618348e-03, 9.93591644e-05], + [1.33808292e01, 5.42489388e00, 1.98989926e00, 2.91867596e-01, 1.31164535e-02, 1.61393456e-04], + [2.45315202e00, 1.41254463e00, 7.79263787e-01, 1.43005758e-01, 7.36618348e-03, 9.93591644e-05], + [0.00000000e00, 4.68668935e-02, 6.29165282e-02, 1.89769059e-02, 1.37627350e-03, 2.38011764e-05], + ], + [ + [0.00000000e00, 5.71924530e-02, 1.52647475e-01, 1.96653382e-01, 1.38081866e-01, 5.10719186e-02], + [2.49656982e00, 1.72375181e00, 1.89064230e00, 1.48193631e00, 7.39051039e-01, 2.13202199e-01], + [1.36176536e01, 6.62008862e00, 4.82787443e00, 3.02455786e00, 1.31597707e00, 3.46313699e-01], + [2.49656982e00, 1.72375181e00, 1.89064230e00, 1.48193631e00, 7.39051039e-01, 2.13202199e-01], + [0.00000000e00, 5.71924530e-02, 1.52647475e-01, 1.96653382e-01, 1.38081866e-01, 5.10719186e-02], ], [ - [0.00000000e00, 1.00434327e00, 9.62188073e-01, 6.92059770e-01, 3.88730984e-01, 1.72346862e-01], - [1.66844725e00, 2.68067203e00, 2.04855005e00, 1.27417395e00, 6.48189376e-01, 2.67538002e-01], - [4.17111812e00, 4.82986815e00, 3.21480841e00, 1.83404693e00, 8.79812781e-01, 3.48046189e-01], - [8.34223623e00, 5.92452728e00, 3.73853409e00, 2.07057816e00, 9.73988987e-01, 3.79902396e-01], - [4.17111812e00, 4.82986815e00, 3.21480841e00, 1.83404693e00, 8.79812781e-01, 3.48046189e-01], - [1.66844725e00, 2.68067203e00, 2.04855005e00, 1.27417395e00, 6.48189376e-01, 2.67538002e-01], - [0.00000000e00, 1.00434327e00, 9.62188073e-01, 6.92059770e-01, 3.88730984e-01, 1.72346862e-01], + [0.00000000e00, 5.81859528e-02, 1.69666475e-01, 2.85860846e-01, 3.45998354e-01, 3.00917744e-01], + [2.49763825e00, 1.75369539e00, 2.10143414e00, 2.15418400e00, 1.85187563e00, 1.25619570e00], + [1.36234814e01, 6.73508730e00, 5.36614469e00, 4.39658175e00, 3.29750686e00, 2.04049388e00], + [2.49763825e00, 1.75369539e00, 2.10143414e00, 2.15418400e00, 1.85187563e00, 1.25619570e00], + [0.00000000e00, 5.81859528e-02, 1.69666475e-01, 2.85860846e-01, 3.45998354e-01, 3.00917744e-01], ], [ - [0.00000000e00, 1.14678447e00, 1.26173994e00, 1.12151632e00, 8.36842100e-01, 5.34290144e-01], - [1.75678621e00, 3.06085890e00, 2.68631207e00, 2.06486049e00, 1.39539214e00, 8.29390894e-01], - [4.39196553e00, 5.51486523e00, 4.21565419e00, 2.97216173e00, 1.89402030e00, 1.07897322e00], - [8.78393106e00, 6.76477462e00, 4.90242805e00, 3.35547203e00, 2.09675848e00, 1.17773021e00], - [4.39196553e00, 5.51486523e00, 4.21565419e00, 2.97216173e00, 1.89402030e00, 1.07897322e00], - [1.75678621e00, 3.06085890e00, 2.68631207e00, 2.06486049e00, 1.39539214e00, 8.29390894e-01], - [0.00000000e00, 1.14678447e00, 1.26173994e00, 1.12151632e00, 8.36842100e-01, 5.34290144e-01], + [0.00000000e00, 5.82239437e-02, 1.71778282e-01, 3.03974560e-01, 4.22126629e-01, 4.85054549e-01], + [2.49481282e00, 1.75484042e00, 2.12759029e00, 2.29068494e00, 2.25933450e00, 2.02488372e00], + [1.36080699e01, 6.73948479e00, 5.43293609e00, 4.67517334e00, 4.02304069e00, 3.28910759e00], + [2.49481282e00, 1.75484042e00, 2.12759029e00, 2.29068494e00, 2.25933450e00, 2.02488372e00], + [0.00000000e00, 5.82239437e-02, 1.71778282e-01, 3.03974560e-01, 4.22126629e-01, 4.85054549e-01], + ], + [ + [0.00000000e00, 5.81602048e-02, 1.71844605e-01, 3.06471593e-01, 4.38129864e-01, 5.44528609e-01], + [2.49158971e00, 1.75291936e00, 2.12841174e00, 2.30950202e00, 2.34498809e00, 2.27316107e00], + [1.35904893e01, 6.73210694e00, 5.43503371e00, 4.71357806e00, 4.17555811e00, 3.69239539e00], + [2.49158971e00, 1.75291936e00, 2.12841174e00, 2.30950202e00, 2.34498809e00, 2.27316107e00], + [0.00000000e00, 5.81602048e-02, 1.71844605e-01, 3.06471593e-01, 4.38129864e-01, 5.44528609e-01], ], ] ) @@ -232,31 +288,39 @@ testingdata_lineardecay_bioscreen = np.array( [ [ - [0.00000000e00, 1.64750580e-01, 2.43834704e-02, 3.08739472e-03, 3.33028964e-04, 2.86132827e-05], - [1.99136794e00, 4.39732393e-01, 5.19137172e-02, 5.68430372e-03, 5.55309057e-04, 4.44170576e-05], - [4.97841984e00, 7.92282480e-01, 8.14686732e-02, 8.18199099e-03, 7.53742693e-04, 5.77831466e-05], - [9.95683968e00, 9.71848303e-01, 9.47407663e-02, 9.23719650e-03, 8.34424206e-04, 6.30719617e-05], - [4.97841984e00, 7.92282480e-01, 8.14686732e-02, 8.18199099e-03, 7.53742693e-04, 5.77831466e-05], - [1.99136794e00, 4.39732393e-01, 5.19137172e-02, 5.68430372e-03, 5.55309057e-04, 4.44170576e-05], - [0.00000000e00, 1.64750580e-01, 2.43834704e-02, 3.08739472e-03, 3.33028964e-04, 2.86132827e-05], + [0.00000000e00, 2.66397630e-02, 2.50186546e-02, 6.31005071e-03, 4.18130412e-04, 6.87752976e-06], + [2.49416415e00, 8.02909075e-01, 3.09872972e-01, 4.75511441e-02, 2.23794568e-03, 2.87105813e-05], + [1.36045317e01, 3.08358153e00, 7.91280190e-01, 9.70495057e-02, 3.98495510e-03, 4.66358586e-05], + [2.49416415e00, 8.02909075e-01, 3.09872972e-01, 4.75511441e-02, 2.23794568e-03, 2.87105813e-05], + [0.00000000e00, 2.66397630e-02, 2.50186546e-02, 6.31005071e-03, 4.18130412e-04, 6.87752976e-06], + ], + [ + [0.00000000e00, 2.87022579e-02, 4.09882298e-02, 3.25232761e-02, 1.65762639e-02, 5.04440452e-03], + [2.50118215e00, 8.65071637e-01, 5.07666970e-01, 2.45088203e-01, 8.87205932e-02, 2.10581111e-02], + [1.36428117e01, 3.32231756e00, 1.29635965e00, 5.00212759e-01, 1.57978625e-01, 3.42056151e-02], + [2.50118215e00, 8.65071637e-01, 5.07666970e-01, 2.45088203e-01, 8.87205932e-02, 2.10581111e-02], + [0.00000000e00, 2.87022579e-02, 4.09882298e-02, 3.25232761e-02, 1.65762639e-02, 5.04440452e-03], + ], + [ + [0.00000000e00, 2.87199369e-02, 4.17753582e-02, 3.65368003e-02, 2.51210294e-02, 1.41395801e-02], + [2.49813835e00, 8.65604474e-01, 5.17416088e-01, 2.75333232e-01, 1.34454461e-01, 5.90263624e-02], + [1.36262092e01, 3.32436393e00, 1.32125463e00, 5.61941349e-01, 2.39413760e-01, 9.58791138e-02], + [2.49813835e00, 8.65604474e-01, 5.17416088e-01, 2.75333232e-01, 1.34454461e-01, 5.90263624e-02], + [0.00000000e00, 2.87199369e-02, 4.17753582e-02, 3.65368003e-02, 2.51210294e-02, 1.41395801e-02], ], [ - [0.00000000e00, 1.64220190e-01, 2.45987826e-02, 3.24153453e-03, 4.00200991e-04, 4.75580882e-05], - [1.98307196e00, 4.38316741e-01, 5.23721285e-02, 5.96809559e-03, 6.67315036e-04, 7.38255155e-05], - [4.95767991e00, 7.89731846e-01, 8.21880621e-02, 8.59048121e-03, 9.05772787e-04, 9.60412692e-05], - [9.91535982e00, 9.68719584e-01, 9.55773511e-02, 9.69836842e-03, 1.00272778e-03, 1.04831800e-04], - [4.95767991e00, 7.89731846e-01, 8.21880621e-02, 8.59048121e-03, 9.05772787e-04, 9.60412692e-05], - [1.98307196e00, 4.38316741e-01, 5.23721285e-02, 5.96809559e-03, 6.67315036e-04, 7.38255155e-05], - [0.00000000e00, 1.64220190e-01, 2.45987826e-02, 3.24153453e-03, 4.00200991e-04, 4.75580882e-05], + [0.00000000e00, 2.86837628e-02, 4.17499178e-02, 3.67112198e-02, 2.59613117e-02, 1.60935121e-02], + [2.49486795e00, 8.64514203e-01, 5.17100991e-01, 2.76647619e-01, 1.38951876e-01, 6.71831461e-02], + [1.36083707e01, 3.32017673e00, 1.32045001e00, 5.64623947e-01, 2.47421996e-01, 1.09128536e-01], + [2.49486795e00, 8.64514203e-01, 5.17100991e-01, 2.76647619e-01, 1.38951876e-01, 6.71831461e-02], + [0.00000000e00, 2.86837628e-02, 4.17499178e-02, 3.67112198e-02, 2.59613117e-02, 1.60935121e-02], ], [ - [0.00000000e00, 1.63523832e-01, 2.44946190e-02, 3.24171843e-03, 4.00357048e-04, 4.76590051e-05], - [1.97466187e00, 4.36458104e-01, 5.21503587e-02, 5.96843416e-03, 6.67575252e-04, 7.39821710e-05], - [4.93665468e00, 7.86383070e-01, 8.18400367e-02, 8.59096855e-03, 9.06125988e-04, 9.62450659e-05], - [9.87330937e00, 9.64611829e-01, 9.51726288e-02, 9.69891861e-03, 1.00311879e-03, 1.05054250e-04], - [4.93665468e00, 7.86383070e-01, 8.18400367e-02, 8.59096855e-03, 9.06125988e-04, 9.62450659e-05], - [1.97466187e00, 4.36458104e-01, 5.21503587e-02, 5.96843416e-03, 6.67575252e-04, 7.39821710e-05], - [0.00000000e00, 1.63523832e-01, 2.44946190e-02, 3.24171843e-03, 4.00357048e-04, 4.76590051e-05], + [0.00000000e00, 2.86461833e-02, 4.16960900e-02, 3.66720177e-02, 2.59766081e-02, 1.62442682e-02], + [2.49159591e00, 8.63381576e-01, 5.16434297e-01, 2.76352200e-01, 1.39033747e-01, 6.78124850e-02], + [1.35905231e01, 3.31582686e00, 1.31874757e00, 5.64021013e-01, 2.47567777e-01, 1.10150799e-01], + [2.49159591e00, 8.63381576e-01, 5.16434297e-01, 2.76352200e-01, 1.39033747e-01, 6.78124850e-02], + [0.00000000e00, 2.86461833e-02, 4.16960900e-02, 3.66720177e-02, 2.59766081e-02, 1.62442682e-02], ], ] ) @@ -265,39 +329,49 @@ [ [ [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [1.26161914, 1.36288124, 0.14490394, 0.0, 0.0, 0.0], - [3.53093985, 2.98502714, 0.74293872, 0.0, 0.0, 0.0], - [7.31314103, 3.78608045, 1.00395844, 0.0, 0.0, 0.0], - [3.53093985, 2.98502714, 0.74293872, 0.0, 0.0, 0.0], - [1.26161914, 1.36288124, 0.14490394, 0.0, 0.0, 0.0], + [2.00991536, 0.0, 0.0, 0.0, 0.0, 0.0], + [12.85529149, 2.48231964, 0.0, 0.0, 0.0, 0.0], + [2.00991536, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + ], + [ + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [2.17586495, 1.14229829, 0.0, 0.0, 0.0, 0.0], + [13.13006415, 6.07379347, 3.01127398, 0.0, 0.0, 0.0], + [2.17586495, 1.14229829, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + ], + [ + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [2.05715049, 1.29444684, 1.06379123, 0.0, 0.0, 0.0], + [12.93350153, 6.27382145, 4.82001743, 3.03997834, 0.0, 0.0], + [2.05715049, 1.29444684, 1.06379123, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], ], [ - [0.0, 0.32682189, 0.18847172, 0.0, 0.0, 0.0], - [1.49351837, 2.26320188, 1.42202775, 0.47810322, 0.0, 0.0], - [3.99231952, 4.49526641, 2.66923241, 1.08783867, 0.0, 0.0], - [8.15698811, 5.59751169, 3.21359039, 1.34042931, 0.09067114, 0.0], - [3.99231952, 4.49526641, 2.66923241, 1.08783867, 0.0, 0.0], - [1.49351837, 2.26320188, 1.42202775, 0.47810322, 0.0, 0.0], - [0.0, 0.32682189, 0.18847172, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [1.91361378, 1.18552918, 1.13897273, 0.68020431, 0.0, 0.0], + [12.69583921, 6.13062863, 4.91331009, 4.01252624, 2.70628757, 0.43809525], + [1.91361378, 1.18552918, 1.13897273, 0.68020431, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], ], [ - [0.0, 0.51828367, 0.56707794, 0.34776144, 0.0, 0.0], - [1.62516006, 2.72758114, 2.18341803, 1.41496002, 0.61359912, 0.0], - [4.25422983, 5.27423754, 3.81764204, 2.4030658, 1.16372612, 0.22197704], - [8.63601278, 6.53183559, 4.53091942, 2.81240115, 1.38439752, 0.33092893], - [4.25422983, 5.27423754, 3.81764204, 2.4030658, 1.16372612, 0.22197704], - [1.62516006, 2.72758114, 2.18341803, 1.41496002, 0.61359912, 0.0], - [0.0, 0.51828367, 0.56707794, 0.34776144, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [1.76869713, 1.05010518, 1.02697617, 0.68965378, 0.13994631, 0.0], + [12.45589207, 5.95258822, 4.77433364, 4.02404611, 3.22495857, 2.14668156], + [1.76869713, 1.05010518, 1.02697617, 0.68965378, 0.13994631, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], ], ] ) testing_massbalance_nodecay_bio = { - "t": np.array([365, 730, 1095]), - "_plume_mass_t": np.array([4673.88175389, 8134.17708612, 10882.8752081]), - "_source_mass_t": np.array([995759.01846931, 991536.02286297, 987330.93690299]), - "_delta_source_t": np.array([4240.98153069, 8463.97713703, 12669.06309701]), + "t": np.array([365, 730, 1095, 1460, 1825]), + "_plume_mass_t": np.array([2547.87145767, 4991.61757796, 6613.25096702, 7190.45509978, 7319.3088115]), + "_source_mass_t": np.array( + [1997376.84643129, 1994757.1333299, 1992140.85618339, 1989528.01048526, 1986918.59173488] + ), + "_delta_source_t": np.array([2623.15356871, 5242.8666701, 7859.14381661, 10471.98951474, 13081.40826512]), "_degraded_mass_t": None, "_electron_acceptor_change_t": None, "_instant_reaction_degraded_mass_t": None, @@ -307,11 +381,13 @@ } testing_massbalance_lineardecay_ana = { - "t": np.array([365, 730, 1095]), - "_plume_mass_t": np.array([1695.49626089, 1689.27120085, 1682.14524122]), - "_source_mass_t": np.array([995759.01846931, 991536.02286297, 987330.93690299]), - "_delta_source_t": np.array([4240.98153069, 8463.97713703, 12669.06309701]), - "_degraded_mass_t": np.array([5009.69193858, 8944.92003192, 11683.6189408]), + "t": np.array([365, 730, 1095, 1460, 1825]), + "_plume_mass_t": np.array([1625.91225957, 1958.42344739, 2011.60695846, 2013.67822011, 2011.28905331]), + "_source_mass_t": np.array( + [1997376.84643129, 1994757.1333299, 1992140.85618339, 1989528.01048526, 1986918.59173488] + ), + "_delta_source_t": np.array([2623.15356871, 5242.8666701, 7859.14381661, 10471.98951474, 13081.40826512]), + "_degraded_mass_t": np.array([1215.93229094, 3312.18208233, 4756.57543617, 5226.36475666, 5319.36988327]), "_electron_acceptor_change_t": None, "_instant_reaction_degraded_mass_t": None, "source_mass_finite": True, @@ -320,170 +396,66 @@ } testing_massbalance_instant_mbt = { - "t": np.array([365, 730, 1095]), - "_plume_mass_t": np.array([5486.89557135, 9074.16458818, 12039.64270149]), - "_source_mass_t": np.array([994988.88209135, 990002.87548539, 985041.85434643]), - "_delta_source_t": np.array([5011.11790865, 9997.12451461, 14958.14565357]), - "_degraded_mass_t": np.array([1415.58024519, 1893.57017734, 1670.60614156]), + "t": np.array([365, 730, 1095, 1460, 1825]), + "_plume_mass_t": np.array([1274.95518137, 2664.20593542, 3767.11945785, 4436.89399159, 4471.80347887]), + "_source_mass_t": np.array( + [1982333.73934155, 1964823.52706592, 1947467.98477742, 1930265.74625588, 1913215.45734916] + ), + "_delta_source_t": np.array([17666.26065845, 35176.47293408, 52532.01522258, 69734.25374412, 86784.54265084]), + "_degraded_mass_t": np.array([1573.35761266, 2634.42858236, 3017.64717164, 2794.12241441, 2838.73740743]), "_electron_acceptor_change_t": { - "oxygen": np.array([1270.70443741, 1827.39892317, 1931.61471693]), - "nitrate": np.array([1270.70443741, 1827.39892317, 1931.61471693]), - "ferrous_iron": np.array([1270.70443741, 1827.39892317, 1931.61471693]), - "sulfate": np.array([1270.70443741, 1827.39892317, 1931.61471693]), - "methane": np.array([1270.70443741, 1827.39892317, 1931.61471693]), + "oxygen": np.array([1936.17805868, 3472.4727154, 4337.57185311, 4529.31972161, 4541.44224634]), + "nitrate": np.array([82.14088734, 147.31702429, 184.01819983, 192.15295789, 192.66724681]), + "ferrous_iron": np.array([19479.12471152, 34935.1800458, 43638.60167375, 45567.70144168, 45689.66138744]), + "sulfate": np.array([26285.08394807, 47141.44777265, 58885.82394531, 61488.94652371, 61653.51898065]), + "methane": np.array([7744.7122347, 13889.89086158, 17350.28741246, 18117.27888645, 18165.76898537]), }, - "_instant_reaction_degraded_mass_t": np.array([2621.77047178, 3770.36578757, 3985.3881663]), + "_instant_reaction_degraded_mass_t": np.array( + [17048.60779573, 30576.1265814, 38193.57469641, 39881.97013662, 39988.71247299] + ), "source_mass_finite": True, "model_degradation": True, "model_instant_reaction": True, } testing_massbalance_instant_mbt_inf = { - "t": np.array([365, 730, 1095]), - "_plume_mass_t": np.array([5539.00845817, 9248.17128318, 12374.47283209]), - "_source_mass_t": np.inf, - "_delta_source_t": np.array([5023.71566351, 10047.43132702, 15071.14699053]), - "_degraded_mass_t": np.array([1404.7379325, 1850.99155079, 1579.25170252]), + "t": np.array([365, 730, 1095, 1460, 1825]), + "_plume_mass_t": np.array([1323.58680137, 2860.86002677, 4194.058875, 5147.84088742, 5451.49338433]), + "_source_mass_t": float("inf"), + "_delta_source_t": np.array([17744.74738066, 35489.49476131, 53234.24214197, 70978.98952263, 88723.73690328]), + "_degraded_mass_t": np.array([1526.73062587, 2445.16948716, 2606.17833265, 2107.91520193, 1893.34623264]), "_electron_acceptor_change_t": { - "oxygen": np.array([1273.22026095, 1831.27321473, 1932.51181133]), - "nitrate": np.array([1273.22026095, 1831.27321473, 1932.51181133]), - "ferrous_iron": np.array([1273.22026095, 1831.27321473, 1932.51181133]), - "sulfate": np.array([1273.22026095, 1831.27321473, 1932.51181133]), - "methane": np.array([1273.22026095, 1831.27321473, 1932.51181133]), + "oxygen": np.array([1940.69129974, 3486.40514084, 4363.93209705, 4567.22770608, 4593.63418442]), + "nitrate": np.array([82.33235817, 147.90809688, 185.13651321, 193.76117541, 194.88145025]), + "ferrous_iron": np.array([19524.53065196, 35075.3486897, 43903.80170365, 45949.07873994, 46214.74391599]), + "sulfate": np.array([26346.35461469, 47330.59100296, 59243.68422661, 62003.57613101, 62362.06407941]), + "methane": np.array([7762.76519897, 13945.62056337, 17455.7283882, 18268.91082431, 18374.53673768]), }, - "_instant_reaction_degraded_mass_t": np.array([2626.961224, 3778.35938776, 3987.23908892]), + "_instant_reaction_degraded_mass_t": np.array( + [17088.34818868, 30698.80561705, 38425.68426828, 40215.76090374, 40448.27758293] + ), "source_mass_finite": False, "model_degradation": True, "model_instant_reaction": True, } testing_massbalance_instant_ana_inf = { - "t": np.array([365, 730, 1095]), - "_plume_mass_t": np.array([5248.12131322, 8706.47934067, 11728.94763204]), - "_source_mass_t": np.inf, - "_delta_source_t": np.array([5023.71566351, 10047.43132702, 15071.14699053]), - "_degraded_mass_t": np.array([1464.30682696, 1957.90718873, 1705.30840063]), + "t": np.array([365, 730, 1095, 1460, 1825]), + "_plume_mass_t": np.array([1307.95320597, 2843.96453853, 4179.04985145, 5156.20424127, 5504.06469115]), + "_source_mass_t": float("inf"), + "_delta_source_t": np.array([17744.74738066, 35489.49476131, 53234.24214197, 70978.98952263, 88723.73690328]), + "_degraded_mass_t": np.array([1535.89343133, 2434.01066762, 2604.5437654, 2108.51531352, 1860.85042709]), "_electron_acceptor_change_t": { - "oxygen": np.array([1269.42660124, 1832.46497738, 1936.67681991]), - "nitrate": np.array([1269.42660124, 1832.46497738, 1936.67681991]), - "ferrous_iron": np.array([1269.42660124, 1832.46497738, 1936.67681991]), - "sulfate": np.array([1269.42660124, 1832.46497738, 1936.67681991]), - "methane": np.array([1269.42660124, 1832.46497738, 1936.67681991]), + "oxygen": np.array([1916.73334639, 3443.78850861, 4332.8979254, 4553.38677448, 4582.8217764]), + "nitrate": np.array([81.31596015, 146.10011855, 183.81991199, 193.17398437, 194.42274203]), + "ferrous_iron": np.array([19283.49912124, 34646.59954116, 43591.57912829, 45809.83057965, 46105.96453829]), + "sulfate": np.array([26021.10724793, 46752.03793506, 58822.37183576, 61815.67499904, 62215.27744925]), + "methane": np.array([7666.93338555, 13775.15403444, 17331.59170161, 18213.54709793, 18331.28710558]), }, - "_instant_reaction_degraded_mass_t": np.array([2619.13398683, 3780.81828224, 3995.8325086]), + "_instant_reaction_degraded_mass_t": np.array( + [16877.39148017, 30323.55384448, 38152.4194111, 40093.88749791, 40353.07120307] + ), "source_mass_finite": False, "model_degradation": True, "model_instant_reaction": True, } - -######## Decrepit ###################################################################################################### - -testing_massbalance_nodecay_dom = { - "time": np.int64(1095), - "source_mass_0": 1000000, - "source_mass_t": np.float64(987330.9369029925), - "source_mass_change": np.float64(12669.063097007456), - "plume_mass_no_decay": np.float64(10884.801775892505), - "transport_outside_extent_nodecay": np.float64(1784.261321114951), -} - -testing_massbalance_lindecay_dom = { - "time": np.int64(1095), - "source_mass_0": 1000000, - "source_mass_t": np.float64(987330.9369029925), - "source_mass_change": np.float64(12669.063097007456), - "plume_mass_no_decay": np.float64(10884.801775892505), - "transport_outside_extent_nodecay": np.float64(1784.261321114951), - "plume_mass_linear_decay": np.float64(1682.1452404554184), - "transport_outside_extent_lineardecay": np.float64(275.74105168269006), - "plume_mass_degraded_linear": np.float64(10711.176804869348), -} - -testing_massbalance_instant_dom = { - "time": np.int64(1095), - "source_mass_0": 1000000, - "source_mass_t": np.float64(987330.9369029925), - "source_mass_change": np.float64(12669.063097007456), - "plume_mass_no_decay": np.float64(10884.801775892505), - "transport_outside_extent_nodecay": np.float64(1784.261321114951), - "source_mass_instant_t": np.float64(985041.8543464276), - "source_mass_instant_change": np.float64(14958.1456535724), - "plume_mass_no_decay_instant_reaction": np.float64(12729.593887083935), - "plume_mass_instant_reaction": np.float64(8758.752888532472), - "plume_mass_degraded_instant": np.float64(3970.8409985514627), - "electron_acceptor_mass_change": np.array( - [1924.56408043, 1924.56408043, 1924.56408043, 1924.56408043, 1924.56408043] - ), -} - -testing_massbalance_instant_dom_inf = { - "time": np.int64(1095), - "source_mass_0": np.inf, - "source_mass_t": np.inf, - "source_mass_change": np.float64(12750.0), - "plume_mass_no_decay": np.float64(10943.20737211077), - "transport_outside_extent_nodecay": np.float64(1806.79262788923), - "source_mass_instant_t": np.inf, - "source_mass_instant_change": np.float64(15071.146990534271), - "plume_mass_no_decay_instant_reaction": np.float64(12810.491497436684), - "plume_mass_instant_reaction": np.float64(8839.650498885221), - "plume_mass_degraded_instant": np.float64(3970.8409985514627), - "electron_acceptor_mass_change": np.array( - [1924.56408043, 1924.56408043, 1924.56408043, 1924.56408043, 1924.56408043] - ), -} -######################################################################################################################## -testing_massbalance_nodecay_kar = { - "time": np.int64(1095), - "source_mass_0": 1000000, - "source_mass_t": np.float64(987330.9369029925), - "source_mass_change": np.float64(12669.063097007456), - "plume_mass_no_decay": np.float64(13710.24884304135), - "transport_outside_extent": 0, -} - -testing_massbalance_lindecay_kar = { - "time": np.int64(1095), - "source_mass_0": 1000000, - "source_mass_t": np.float64(987330.9369029925), - "source_mass_change": np.float64(12669.063097007456), - "plume_mass_no_decay": np.float64(13710.24884304135), - "transport_outside_extent": 0, - "plume_mass_linear_decay": np.float64(1711.1646817402216), - "transport_outside_extent_lineardecay": np.float64(0.0), - "plume_mass_degraded_linear": np.float64(11999.084161301129), -} - -testing_massbalance_instant_kar = { - "time": np.int64(1095), - "source_mass_0": 1000000, - "source_mass_t": np.float64(987330.9369029925), - "source_mass_change": np.float64(12669.063097007456), - "plume_mass_no_decay": np.float64(13710.24884304135), - "transport_outside_extent": 0, - "source_mass_instant_t": np.float64(985041.8543464276), - "source_mass_instant_change": np.float64(14958.1456535724), - "plume_mass_no_decay_instant_reaction": np.float64(16025.030867780724), - "plume_mass_instant_reaction": np.float64(12039.642701485522), - "plume_mass_degraded_instant": np.float64(3985.3881662952026), - "electron_acceptor_mass_change": np.array( - [1931.61471693, 1931.61471693, 1931.61471693, 1931.61471693, 1931.61471693] - ), -} - -testing_massbalance_instant_kar_inf = { - "time": np.int64(1095), - "source_mass_0": np.inf, - "source_mass_t": np.inf, - "source_mass_change": np.float64(12750.0), - "plume_mass_no_decay": np.float64(13953.724534605748), - "transport_outside_extent": 0, - "source_mass_instant_t": np.inf, - "source_mass_instant_change": np.float64(15071.146990534271), - "plume_mass_no_decay_instant_reaction": np.float64(16361.711921001373), - "plume_mass_instant_reaction": np.float64(12374.472832085463), - "plume_mass_degraded_instant": np.float64(3987.2390889159105), - "electron_acceptor_mass_change": np.array( - [1932.51181133, 1932.51181133, 1932.51181133, 1932.51181133, 1932.51181133] - ), -} diff --git a/tests/test_mass_balance.py b/tests/test_mass_balance.py index df0bdba..8d64cf3 100644 --- a/tests/test_mass_balance.py +++ b/tests/test_mass_balance.py @@ -10,6 +10,7 @@ from mibitrans.transport.models import Anatrans from mibitrans.transport.models import Bioscreen from mibitrans.transport.models import Mibitrans +from tests.conftest import electron_acceptor_dict from tests.test_example_data import testing_massbalance_instant_ana_inf from tests.test_example_data import testing_massbalance_instant_mbt from tests.test_example_data import testing_massbalance_instant_mbt_inf @@ -20,17 +21,24 @@ @pytest.fixture(scope="module") def test_model_pars(): """ModelParameters fixture with increased spatial resolution, specifically for testing mass balance.""" - return ModelParameters(model_length=50, model_width=30, model_time=3 * 365, dx=1, dy=1, dt=1 * 365) + return ModelParameters( + model_length=100, # [m] + model_width=40, # [m] + model_time=5 * 365, # [days] + dx=1, # [m] + dy=1, # [m] + dt=365, # [days] + ) @pytest.fixture(scope="module") def test_source_pars_inf(): """SourceParameters fixture with example data for tests.""" return SourceParameters( - source_zone_boundary=np.array([5, 10, 15]), - source_zone_concentration=np.array([10, 5, 2]), - depth=10, - total_mass="inf", + source_zone_boundary=np.array([2, 11, 20]), # [m] + source_zone_concentration=np.array([13.68, 2.508, 0.057]), # [g/m3] + depth=3, # [m] + total_mass=np.inf, # [g] ) @@ -54,7 +62,7 @@ def test_anatrans_lineardecay_model_mb(test_hydro_pars, test_att_pars, test_sour def test_mibitrans_instantreaction_model_mb(test_hydro_pars, test_att_pars, test_source_pars, test_model_pars): """Mibitrans with instant reaction fixture mass balance object for testing.""" obj = Mibitrans(test_hydro_pars, test_att_pars, test_source_pars, test_model_pars) - obj.instant_reaction(dict(delta_oxygen=0.5, delta_nitrate=0.5, ferrous_iron=0.5, delta_sulfate=0.5, methane=0.5)) + obj.instant_reaction(electron_acceptor_dict) res = obj.run() return res.mass_balance() @@ -63,16 +71,16 @@ def test_mibitrans_instantreaction_model_mb(test_hydro_pars, test_att_pars, test def test_mibitrans_instantreaction_model_mb_inf(test_hydro_pars, test_att_pars, test_source_pars_inf, test_model_pars): """Mibitrans with instant reaction and infinite source mass fixture mass balance object for testing.""" obj = Mibitrans(test_hydro_pars, test_att_pars, test_source_pars_inf, test_model_pars) - obj.instant_reaction(dict(delta_oxygen=0.5, delta_nitrate=0.5, ferrous_iron=0.5, delta_sulfate=0.5, methane=0.5)) + obj.instant_reaction(electron_acceptor_dict) res = obj.run() return res.mass_balance() @pytest.fixture(scope="module") def test_anatrans_instantreaction_model_mb_inf(test_hydro_pars, test_att_pars, test_source_pars_inf, test_model_pars): - """Anatrans with instant reaction and infinite source mass fixture mass balance object for testing.""" + """Anatrans with instant reaction and infinite source mass fixture mass balance object for testing.""" obj = Anatrans(test_hydro_pars, test_att_pars, test_source_pars_inf, test_model_pars) - obj.instant_reaction(dict(delta_oxygen=0.5, delta_nitrate=0.5, ferrous_iron=0.5, delta_sulfate=0.5, methane=0.5)) + obj.instant_reaction(electron_acceptor_dict) res = obj.run() return res.mass_balance() diff --git a/tests/test_mibitrans.py b/tests/test_mibitrans.py index 9693322..20693ae 100644 --- a/tests/test_mibitrans.py +++ b/tests/test_mibitrans.py @@ -24,8 +24,8 @@ def test_transport_equation_numerical_mibitrans(model, expected, request): @pytest.mark.parametrize( "x, y, t, expected", [ - (16, 0, 393, 0.2980583920684923), - (24, -5, 283, 0.03725197246248769), + (16, 0, 393, 4.6652115692165195), + (24, -5, 527, 1.7909410090260753), (-16, 0, 393, DomainValueError), ("nonsense", 0, 393, TypeError), (16, "nonsense", 393, TypeError), @@ -46,8 +46,8 @@ def test_mibitrans_linear_sample(x, y, t, expected, test_mibitrans_model_lineard @pytest.mark.parametrize( "x, y, t, expected", [ - (20, 0, 476, 3.8101869779573443), - (11, 7, 193, 2.0276832492832924), + (20, 0, 476, 6.3213083960634435), + (35, 7, 745, 2.5356628358944633), (-16, 0, 393, DomainValueError), ("nonsense", 0, 393, TypeError), (16, "nonsense", 393, TypeError), From 578284b710d7acba0d6efd65945f085d626829c2 Mon Sep 17 00:00:00 2001 From: Bakker Date: Tue, 10 Feb 2026 11:10:14 +0100 Subject: [PATCH 15/19] Split up equation formulation into two. Equation formulation example attempted to show two different aspects of the formulation of the equation, and therefore accomplished neither. They have now been split up for the sake of clarity. --- examples/example_keesler.ipynb | 112 +++++++++-- ...xample_mibitrans_anatrans_comparison.ipynb | 5 +- examples/example_source_decay.ipynb | 177 ++++++++++++++++++ ...nb => example_validity_substitution.ipynb} | 125 +++---------- 4 files changed, 295 insertions(+), 124 deletions(-) create mode 100644 examples/example_source_decay.ipynb rename examples/{example_equation_formulation.ipynb => example_validity_substitution.ipynb} (64%) diff --git a/examples/example_keesler.ipynb b/examples/example_keesler.ipynb index bb38010..e77e2b0 100644 --- a/examples/example_keesler.ipynb +++ b/examples/example_keesler.ipynb @@ -25,16 +25,10 @@ "In 1987, during their removal, multiple underground storage tanks (UST) leaked a mixture of BTEX and lead to the groundwater. Remediation efforts included a 'bioventing' system and an in well aeration system. Monitoring wells were used to evaluate natural attenuation [2,3]. Local geology is a fine- to medium-grained sand, underlain by a clay layer at 20ft depth. Bottom of sandy layer has local presence of peat. Thickness and continuity of clay layer are unknown, but not every boring showed the clay layer at 20ft. Assumption of clay continuous clay layer was made. Unconfined aquifer with groundwater levels varying between 5 and 9 ft below ground level. Groundwater flow is to the north-east, which eventually discharges into the Back Bay of Biloxi, 2100ft downgradient [4]. Values of hydraulic gradient and conductivity vary, with values of 0.003 to 0.0083 ft/ft and 40, 61, 32 ft/day listed in [4]. In [1]. the values of 0.003 ft/ft and 0.011 cm/sec (31.2 ft/day) were used. Effective porosity is estimated to be 0.25 [4], resulting in groundwater velocity of 0.8 ft/day. Although [1] uses an effective porosity of 0.3, and results in groundwater velocity of 0.31 ft/day. For consistency, values reported in [1] are used in the modelling. Dispersivity values were based on estimated plume length of 280ft [1]. With 13.3ft, 1.3ft and 0ft for longitudinal, transverse horizontal and transverse vertical dispersivity respectively. After calibration, these values were changed to 32.5ft, 3.25ft and 0ft respectively [1]." ] }, - { - "cell_type": "markdown", - "id": "1d69d4356c2cc7f9", - "metadata": {}, - "source": [] - }, { "cell_type": "code", "execution_count": null, - "id": "e9bc7c74150eecf0", + "id": "f868ccbb35cc62f3", "metadata": {}, "outputs": [], "source": [ @@ -44,8 +38,8 @@ " h_conductivity = 0.011 / 100 * 3600 * 24, #m/d\n", " h_gradient = 0.003, # m/m\n", " porosity = 0.3,\n", - " alpha_x = 13.3 /ft,#32.5 / ft, #m\n", - " alpha_y = 1.3/ft, #3.25 / ft, #m\n", + " alpha_x = 13.3 /ft,\n", + " alpha_y = 1.3/ft,\n", " alpha_z = 0\n", ")\n", "print(hydro_pars.velocity)\n", @@ -238,7 +232,7 @@ { "cell_type": "code", "execution_count": null, - "id": "49cf81bb814503c9", + "id": "ab773f981180e3f7", "metadata": {}, "outputs": [], "source": [ @@ -250,10 +244,18 @@ "plt.show()" ] }, + { + "cell_type": "markdown", + "id": "4621161235a48efc", + "metadata": {}, + "source": [ + "The models above do not consider biodegradation. Using the electron acceptor concentrations, a biodegradation capacity (BC) is determined and applied to the model." + ] + }, { "cell_type": "code", "execution_count": null, - "id": "d7f2a3f0f078a615", + "id": "fb54e5a725c36ff6", "metadata": {}, "outputs": [], "source": [ @@ -267,16 +269,16 @@ }, { "cell_type": "markdown", - "id": "15da159822122e75", + "id": "db4ba13e0de6882a", "metadata": {}, "source": [ - "At the plume fringes, electron acceptor concentrations are high enough to allow for full biodegradation of the" + "At the plume fringes, electron acceptor concentrations remain high enough to allow for full biodegradation of the contaminant plume." ] }, { "cell_type": "code", "execution_count": null, - "id": "deff0aeaac4b0bd3", + "id": "29b7a6eb6edf945a", "metadata": {}, "outputs": [], "source": [ @@ -300,7 +302,7 @@ { "cell_type": "code", "execution_count": null, - "id": "9920535a966bbfc4", + "id": "9cf9686cf13961ea", "metadata": {}, "outputs": [], "source": [ @@ -313,15 +315,24 @@ "\n", "field_data_x = np.array([0, 32/ft, 64/ft, 192/ft, 288/ft])\n", "field_data_c = np.array([12,5,1,0.5,0.001])\n", - "plt.scatter(field_data_x, field_data_c, color=\"black\")\n", + "plt.scatter(field_data_x, field_data_c, label=\"field data\", color=\"black\")\n", "plt.xlim(0,150)\n", + "plt.legend()\n", "plt.show()" ] }, + { + "cell_type": "markdown", + "id": "d26bd567fade9a58", + "metadata": {}, + "source": [ + "Prediction is off from the field data. As mentioned before, dispersivity was calibrated to better represent observed concentrations." + ] + }, { "cell_type": "code", "execution_count": null, - "id": "4e58b23852553780", + "id": "7c19d1d712c57336", "metadata": {}, "outputs": [], "source": [ @@ -348,7 +359,15 @@ { "cell_type": "code", "execution_count": null, - "id": "cdfc0b049e63ce9c", + "id": "c54c146347428660", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7a836625240e2a3e", "metadata": {}, "outputs": [], "source": [ @@ -365,10 +384,18 @@ "plt.show()" ] }, + { + "cell_type": "markdown", + "id": "412d2cbae313db57", + "metadata": {}, + "source": [ + "Instead of comparing to instant reaction model, use linear decay model." + ] + }, { "cell_type": "code", "execution_count": null, - "id": "43aa262a855fa057", + "id": "d8f90e8c373cbbc6", "metadata": {}, "outputs": [], "source": [ @@ -387,7 +414,52 @@ { "cell_type": "code", "execution_count": null, - "id": "35b3a013caff9f68", + "id": "31b9248ddafadb04", + "metadata": {}, + "outputs": [], + "source": [ + "time_point = 365 * 6\n", + "plt.figure()\n", + "mbt_results_linear.centerline(time=time_point, label=\"Mibitrans\", color=\"blue\")\n", + "ana_results_linear.centerline(time=time_point, label=\"Anatrans\", color=\"red\", linestyle=\"--\")\n", + "bio_results_linear.centerline(time=time_point, label=\"Bioscreen\", color=\"green\", linestyle=\":\")\n", + "plt.title(\"Comparison linear decay models to field data, t=6 years\")\n", + "plt.scatter(field_data_x, field_data_c, label=\"field data\", color=\"black\")\n", + "plt.xlim(-5,150)\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "64397222b20cb884", + "metadata": {}, + "outputs": [], + "source": [ + "new_hydrology = mbt.HydrologicalParameters(\n", + " h_conductivity = 0.011 / 100 * 3600 * 24, #m/d\n", + " h_gradient = 0.003, # m/m\n", + " porosity = 0.3,\n", + " alpha_x = 13.3 /ft,\n", + " alpha_y = 1.3/ft,\n", + " alpha_z = 0\n", + ")\n", + "mbt_model.hydrological_parameters = new_hydrology\n", + "ana_model.hydrological_parameters = new_hydrology\n", + "bio_model.hydrological_parameters = new_hydrology\n", + "mbt_model.mode = \"linear\"\n", + "ana_model.mode = \"linear\"\n", + "bio_model.mode = \"linear\"\n", + "bio_results_linear = bio_model.run()\n", + "ana_results_linear = ana_model.run()\n", + "mbt_results_linear = mbt_model.run()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "406077924c0f2889", "metadata": {}, "outputs": [], "source": [ diff --git a/examples/example_mibitrans_anatrans_comparison.ipynb b/examples/example_mibitrans_anatrans_comparison.ipynb index d60676b..3fe1cb1 100644 --- a/examples/example_mibitrans_anatrans_comparison.ipynb +++ b/examples/example_mibitrans_anatrans_comparison.ipynb @@ -96,7 +96,7 @@ " # Model extent in the transverse horizontal (y) direction in [m].\n", " model_width = 150,\n", " # Model duration in [days].\n", - " model_time = 5*365,\n", + " model_time = 10*365,\n", " # Model grid discretization step size in the longitudinal (x) direction, in [m].\n", " dx = 2,\n", " # Model grid discretization step size in the transverse horizontal (y) direction, in [m].\n", @@ -392,7 +392,7 @@ " time_parameter=mbt_res_va.t,\n", " y_colors=[\"darkgreen\", \"limegreen\", \"cornflowerblue\", \"blue\", \"indigo\",\n", " \"green\", \"greenyellow\", \"darkturquoise\", \"dodgerblue\", \"violet\"],\n", - " y_names=[\"Mibitrans $\\alpha_l=10$\", r\"Mibitrans $\\alpha_l=5$\", r\"Mibitrans $\\alpha_l=1$\",\n", + " y_names=[r\"Mibitrans $\\alpha_l=10$\", r\"Mibitrans $\\alpha_l=5$\", r\"Mibitrans $\\alpha_l=1$\",\n", " r\"Mibitrans $\\alpha_l=0.5$\", r\"Mibitrans $\\alpha_l=0.1$\", r\"Anatrans $\\alpha_l=10$\",\n", " r\"Anatrans $\\alpha_l=5$\", r\"Anatrans $\\alpha_l=1$\", r\"Anatrans $\\alpha_l=0.5$\",\n", " r\"Anatrans $\\alpha_l=0.1$\",\n", @@ -450,6 +450,7 @@ "ax.set_ylabel(\"Distance from plume center (m)\")\n", "\n", "def update(frame):\n", + " \"\"\"Update animation frame to the next frame.\"\"\"\n", " mesh.set_array(err_abs[frame, 35:115, :150])\n", " ax.set_title(f\"Concentration distribution at t={mbt_va.t[frame]} days\")\n", " return mesh\n", diff --git a/examples/example_source_decay.ipynb b/examples/example_source_decay.ipynb new file mode 100644 index 0000000..3dd756d --- /dev/null +++ b/examples/example_source_decay.ipynb @@ -0,0 +1,177 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "1bb35746ac89a12", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import mibitrans as mbt\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "47a67bc4465e9ddb", + "metadata": {}, + "outputs": [], + "source": [ + "ft = 3.281\n", + "hydro = mbt.HydrologicalParameters(\n", + " velocity=350/ft/365,\n", + " h_gradient=0.048, # Hydraulic gradient [-]\n", + " h_conductivity=0.495, # Hydraulic conductivity [m/day]\n", + " porosity=0.25, # Effective soil porosity [-]\n", + " alpha_x=13.3/ft, # Longitudinal dispersivity, in [m]\n", + " alpha_y=1.3/ft, # Transverse horizontal dispersivity, in [m]\n", + " alpha_z=0 # Transverse vertical dispersivity, in [m]\n", + ")\n", + "\n", + "att = mbt.AttenuationParameters(\n", + " # Soil bulk density in [g/m^3]\n", + " bulk_density=1.7,\n", + " # Partition coefficient of the transported contaminant to soil organic matter, in [m^3/g]\n", + " partition_coefficient=38,\n", + " # Fraction of organic material in the soil [-]\n", + " fraction_organic_carbon=5.7e-5,\n", + " # Molecular diffusion, in [m2/day]\n", + " diffusion=0,\n", + " # Contaminant half life, in [days]\n", + " half_life=0\n", + ")\n", + "\n", + "source = mbt.SourceParameters(\n", + " source_zone_boundary=np.array([30/ft, 35/ft, 40/ft]),\n", + " source_zone_concentration=np.array([13.68, 2.508, 0.057]),\n", + " depth=10/ft,\n", + " total_mass=25000\n", + ")\n", + "\n", + "model = mbt.ModelParameters(\n", + " # Model extent in the longitudinal (x) direction in [m].\n", + " model_length = 1000/ft,\n", + " # Model extent in the transverse horizontal (y) direction in [m].\n", + " model_width = 300/ft,\n", + " # Model duration in [days].\n", + " model_time = 10 * 365,\n", + " # Model grid discretization step size in the longitudinal (x) direction, in [m].\n", + " dx = 5/ft,\n", + " # Model grid discretization step size in the transverse horizontal (y) direction, in [m].\n", + " dy = 2/ft,\n", + " # Model time discretization step size, in [days]\n", + " dt = 365/5\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "210f6092a5188ee", + "metadata": {}, + "outputs": [], + "source": [ + "class AnatransAlternativeSource(mbt.Anatrans):\n", + " \"\"\"Mibitrans model class with alternative calculations.\"\"\"\n", + "\n", + " def __init__(\n", + " self,\n", + " hydrological_parameters,\n", + " attenuation_parameters,\n", + " source_parameters,\n", + " model_parameters,\n", + " verbose=False,\n", + " ):\n", + " \"\"\"Initialize model class.\"\"\"\n", + " super().__init__(hydrological_parameters, attenuation_parameters, source_parameters, model_parameters, verbose)\n", + "\n", + " def _equation_term_source_depletion(self, xxx, ttt):\n", + " term = np.exp(-self.k_source * (ttt - xxx / self.rv))\n", + " # Term can be max 1; can not have 'generation' of solute ahead of advection.\n", + " return np.where(term > 1, 1, term)\n", + "\n", + " def _equation_decay_sqrt(self):\n", + " return np.sqrt(1 + 4 * self._decay_rate * self._hyd_pars.alpha_x / self.rv)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1af5e77b663d6c36", + "metadata": {}, + "outputs": [], + "source": [ + "ana_object = mbt.Anatrans(hydro, att, source, model)\n", + "ana_results = ana_object.run()\n", + "\n", + "alt_object = AnatransAlternativeSource(hydro, att, source, model)\n", + "alt_results = alt_object.run()\n", + "\n", + "mbt_object = mbt.Mibitrans(hydro, att, source, model, verbose=True)\n", + "mbt_results = mbt_object.run()\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8f3caf71b2e44f2e", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib ipympl" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7f6401bd8fb02806", + "metadata": {}, + "outputs": [], + "source": [ + "plt.clf()\n", + "ana_results.centerline(color=\"green\", label=\"anatrans\")\n", + "alt_results.centerline(color=\"red\", linestyle=\"--\", label=\"anatrans alternative\")\n", + "mbt_results.centerline(color=\"blue\", linestyle=\":\", label=\"mibitrans\")\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ba19cf9692813b01", + "metadata": {}, + "outputs": [], + "source": [ + "plt.clf()\n", + "anim = mbt.centerline(model=[ana_results, alt_results, mbt_results],\n", + " legend_names=[\"Anatrans model\", \"Alternative model\", \"mibitrans model\"],\n", + " linestyle=\":\",\n", + " animate=True)\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/example_equation_formulation.ipynb b/examples/example_validity_substitution.ipynb similarity index 64% rename from examples/example_equation_formulation.ipynb rename to examples/example_validity_substitution.ipynb index 18ca9df..7011dd9 100644 --- a/examples/example_equation_formulation.ipynb +++ b/examples/example_validity_substitution.ipynb @@ -16,7 +16,7 @@ { "cell_type": "code", "execution_count": null, - "id": "fb4e31a6383d8c4a", + "id": "47a67bc4465e9ddb", "metadata": {}, "outputs": [], "source": [ @@ -26,8 +26,8 @@ " h_gradient=0.048, # Hydraulic gradient [-]\n", " h_conductivity=0.495, # Hydraulic conductivity [m/day]\n", " porosity=0.25, # Effective soil porosity [-]\n", - " alpha_x=13.3/ft, # Longitudinal dispersivity, in [m]\n", - " alpha_y=1.3/ft, # Transverse horizontal dispersivity, in [m]\n", + " alpha_x=13.3/ft/5, # Longitudinal dispersivity, in [m]\n", + " alpha_y=1.3/ft/5, # Transverse horizontal dispersivity, in [m]\n", " alpha_z=0 # Transverse vertical dispersivity, in [m]\n", ")\n", "\n", @@ -70,68 +70,7 @@ { "cell_type": "code", "execution_count": null, - "id": "3e7374d25890988b", - "metadata": {}, - "outputs": [], - "source": [ - "class AnatransAlternativeSource(mbt.Anatrans):\n", - " \"\"\"Mibitrans model class with alternative calculations.\"\"\"\n", - "\n", - " def __init__(\n", - " self,\n", - " hydrological_parameters,\n", - " attenuation_parameters,\n", - " source_parameters,\n", - " model_parameters,\n", - " verbose=False,\n", - " ):\n", - " \"\"\"Initialize model class.\"\"\"\n", - " super().__init__(hydrological_parameters, attenuation_parameters, source_parameters, model_parameters, verbose)\n", - "\n", - " def _equation_term_source_decay(self, xxx, ttt):\n", - " #adapted source term\n", - " term = np.exp(-self.k_source * ttt)\n", - " # Term can be max 1; can not have 'generation' of solute ahead of advection.\n", - " return np.where(term > 1, 1, term)\n", - "\n", - " def _calculate_concentration_for_all_xyt(self, xxx, yyy, ttt):\n", - " cxyt = 0\n", - " #adapted decay_sqrt\n", - " decay_sqrt = np.sqrt(1 + 4 * (self._decay_rate - self.k_source) * self._hyd_pars.alpha_x / self.rv)\n", - " x_term = self._equation_term_x(xxx, ttt, decay_sqrt)\n", - " additional_x = self._equation_term_additional_x(xxx, ttt, decay_sqrt)\n", - " z_term = self._equation_term_z(xxx)\n", - " source_decay = self._equation_term_source_decay(xxx, ttt)\n", - " for i in range(len(self.c_source)):\n", - " y_term = self._equation_term_y(i, xxx, yyy)\n", - " cxyt_step = 1 / 8 * self.c_source[i] * source_decay * (x_term + additional_x) * y_term * z_term\n", - " cxyt += cxyt_step\n", - " if self._mode == \"instant_reaction\":\n", - " self.cxyt_noBC = cxyt.copy()\n", - " cxyt -= self.biodegradation_capacity\n", - " cxyt = np.where(cxyt < 0, 0, cxyt)\n", - " self.has_run = True\n", - " return cxyt" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1af5e77b663d6c36", - "metadata": {}, - "outputs": [], - "source": [ - "ana_object = mbt.Anatrans(hydro, att, source, model)\n", - "ana_results = ana_object.run()\n", - "\n", - "alt_object = AnatransAlternativeSource(hydro, att, source, model)\n", - "alt_results = alt_object.run()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8f3caf71b2e44f2e", + "id": "4ce54f3015742952", "metadata": {}, "outputs": [], "source": [ @@ -141,35 +80,7 @@ { "cell_type": "code", "execution_count": null, - "id": "7f6401bd8fb02806", - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "ana_results.centerline(color=\"green\")\n", - "alt_results.centerline(color=\"red\", linestyle=\"--\")\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ba19cf9692813b01", - "metadata": {}, - "outputs": [], - "source": [ - "plt.clf()\n", - "anim = mbt.centerline(model=[ana_results, alt_results],\n", - " legend_names=[\"Anatrans model\", \"Alternative model\"],\n", - " linestyle=\":\",\n", - " animate=True)\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a1d736e61b017e54", + "id": "858c8ae1fc0c6ff5", "metadata": {}, "outputs": [], "source": [ @@ -220,15 +131,25 @@ { "cell_type": "code", "execution_count": null, - "id": "79ba321701becd93", + "id": "9c7df9302fb5805f", "metadata": {}, "outputs": [], "source": [ - "plt.clf()\n", + "ana_object = mbt.Anatrans(hydro, att, source, model)\n", + "ana_results = ana_object.run()\n", "alt_disp_object = AnatransAlternativeDispersion(hydro, att, source, model)\n", - "alt_disp_results = alt_disp_object.run()\n", + "alt_disp_results = alt_disp_object.run()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4deae7fe281c14fb", + "metadata": {}, + "outputs": [], + "source": [ + "plt.clf()\n", "ana_results.centerline(color=\"green\")\n", - "alt_results.centerline(color=\"red\", linestyle=\"--\")\n", "alt_disp_results.centerline(color=\"blue\", linestyle=\"-.\")\n", "plt.show()" ] @@ -236,13 +157,13 @@ { "cell_type": "code", "execution_count": null, - "id": "c2ef5cb8f80d1879", + "id": "d96482cdc4854c1e", "metadata": {}, "outputs": [], "source": [ "plt.clf()\n", - "anim = mbt.centerline(model=[ana_results, alt_results, alt_disp_results],\n", - " legend_names=[\"Anatrans model\", \"Alternative model\", \"Alt disp model\"],\n", + "anim = mbt.centerline(model=[ana_results, alt_disp_results],\n", + " legend_names=[\"Anatrans model\", \"Alt disp model\"],\n", " linestyle=\":\",\n", " animate=True)\n", "plt.show()" @@ -251,7 +172,7 @@ { "cell_type": "code", "execution_count": null, - "id": "3df1125ff510edea", + "id": "b2948327fd1a2e3f", "metadata": {}, "outputs": [], "source": [ From 9ce2a976c877d645ede353987569cebf321c6c63 Mon Sep 17 00:00:00 2001 From: Bakker Date: Tue, 10 Feb 2026 15:32:21 +0100 Subject: [PATCH 16/19] Finished base of source decay example Additionally, some elaboration on Keesler example --- examples/example_keesler.ipynb | 34 ++- examples/example_source_decay.ipynb | 357 +++++++++++++++++++++++----- 2 files changed, 317 insertions(+), 74 deletions(-) diff --git a/examples/example_keesler.ipynb b/examples/example_keesler.ipynb index e77e2b0..de99624 100644 --- a/examples/example_keesler.ipynb +++ b/examples/example_keesler.ipynb @@ -357,12 +357,12 @@ ] }, { - "cell_type": "code", - "execution_count": null, - "id": "c54c146347428660", + "cell_type": "markdown", + "id": "44af697835da362d", "metadata": {}, - "outputs": [], - "source": [] + "source": [ + "With adapted dispersivity values, model predicts contaminant distribution better. But does not represent the steep drop in concentration well." + ] }, { "cell_type": "code", @@ -411,6 +411,14 @@ "mbt_results_linear = mbt_model.run()" ] }, + { + "cell_type": "markdown", + "id": "d9e312a3a0e67643", + "metadata": {}, + "source": [ + "Linear decay model with given half life represents the field data well. Although modelled concentrations are lower than the field data at the latter part of the plume." + ] + }, { "cell_type": "code", "execution_count": null, @@ -430,6 +438,14 @@ "plt.show()" ] }, + { + "cell_type": "markdown", + "id": "10b6d77b3e52e17d", + "metadata": {}, + "source": [ + "Changing the dispersivity in the linear decay model has only a slight influence on the modelled results; the influence of contaminant decay overshadows the effects of dispersivity." + ] + }, { "cell_type": "code", "execution_count": null, @@ -475,14 +491,6 @@ "plt.show()" ] }, - { - "cell_type": "markdown", - "id": "9020b5a55a397f08", - "metadata": {}, - "source": [ - "According to Bioscreen, the above model predicts the measured field concentrations fairly well" - ] - }, { "cell_type": "markdown", "id": "eb969db6ab0c1424", diff --git a/examples/example_source_decay.ipynb b/examples/example_source_decay.ipynb index 3dd756d..7140c30 100644 --- a/examples/example_source_decay.ipynb +++ b/examples/example_source_decay.ipynb @@ -9,7 +9,19 @@ "source": [ "import matplotlib.pyplot as plt\n", "import numpy as np\n", - "import mibitrans as mbt\n" + "import mibitrans as mbt" + ] + }, + { + "cell_type": "markdown", + "id": "83193b7ca94fe709", + "metadata": {}, + "source": [ + "# Formulation of the source depletion term\n", + "\n", + "In the original BIOSCREEN, the source depletion term is expressed as; $C_0 \\exp(-k_s(t - \\frac{x}{v}))$. Although this represents both the decline in source concentration over time, and that concentrations at the advective front originate from the initial source concentration. However, this expression does not come forth from the exact solution, but is instead superimposed onto the analytical solution. When taking the exact solution of Karanovic (2007) as base, expression of source depletion is integrated into the longitudinal advection and dispersion term. This, mathematically sound, method of source depletion is implemented in the Anatrans model class.\n", + "\n", + "Below, differences between the two implementations and the Mibitrans solution are visualized. Simultaneously, this example showcases how to adapt a model class to fit the desired behaviour." ] }, { @@ -21,51 +33,51 @@ "source": [ "ft = 3.281\n", "hydro = mbt.HydrologicalParameters(\n", - " velocity=350/ft/365,\n", - " h_gradient=0.048, # Hydraulic gradient [-]\n", - " h_conductivity=0.495, # Hydraulic conductivity [m/day]\n", - " porosity=0.25, # Effective soil porosity [-]\n", - " alpha_x=13.3/ft, # Longitudinal dispersivity, in [m]\n", - " alpha_y=1.3/ft, # Transverse horizontal dispersivity, in [m]\n", - " alpha_z=0 # Transverse vertical dispersivity, in [m]\n", + " velocity=350/ft/365, # [m/days]\n", + " h_gradient=0.048, # [-]\n", + " h_conductivity=0.495, # [m/day]\n", + " porosity=0.25, # [-]\n", + " alpha_x=13.3/ft, # [m]\n", + " alpha_y=1.13/ft, # [m]\n", + " alpha_z=0 # T[m]\n", ")\n", "\n", "att = mbt.AttenuationParameters(\n", - " # Soil bulk density in [g/m^3]\n", - " bulk_density=1.7,\n", - " # Partition coefficient of the transported contaminant to soil organic matter, in [m^3/g]\n", - " partition_coefficient=38,\n", - " # Fraction of organic material in the soil [-]\n", - " fraction_organic_carbon=5.7e-5,\n", - " # Molecular diffusion, in [m2/day]\n", - " diffusion=0,\n", - " # Contaminant half life, in [days]\n", - " half_life=0\n", + " bulk_density=1.7, #[g/m3]\n", + "\n", + " partition_coefficient=38, # [m^3/g]\n", + " fraction_organic_carbon=5.7e-5, # [-]\n", + " diffusion=0, # [m2/day]\n", + " half_life=0 # [days]\n", ")\n", "\n", "source = mbt.SourceParameters(\n", - " source_zone_boundary=np.array([30/ft, 35/ft, 40/ft]),\n", - " source_zone_concentration=np.array([13.68, 2.508, 0.057]),\n", - " depth=10/ft,\n", - " total_mass=25000\n", + " source_zone_boundary=np.array([30/ft, 35/ft, 40/ft]), # [m]\n", + " source_zone_concentration=np.array([13.68, 2.508, 0.057]), # [g/m3]\n", + " depth=10/ft, # [m]\n", + " total_mass=25000 # [g]\n", ")\n", "\n", "model = mbt.ModelParameters(\n", - " # Model extent in the longitudinal (x) direction in [m].\n", - " model_length = 1000/ft,\n", - " # Model extent in the transverse horizontal (y) direction in [m].\n", - " model_width = 300/ft,\n", - " # Model duration in [days].\n", - " model_time = 10 * 365,\n", - " # Model grid discretization step size in the longitudinal (x) direction, in [m].\n", - " dx = 5/ft,\n", - " # Model grid discretization step size in the transverse horizontal (y) direction, in [m].\n", - " dy = 2/ft,\n", - " # Model time discretization step size, in [days]\n", - " dt = 365/5\n", + " model_length = 1500/ft, # [m]\n", + " model_width = 300/ft, # [m]\n", + " model_time = 10 * 365, # [days]\n", + " dx = 5/ft, # [m]\n", + " dy = 2/ft, # [m]\n", + " dt = 365/5 # [days]\n", ")" ] }, + { + "cell_type": "markdown", + "id": "21df3fcbe62407b4", + "metadata": {}, + "source": [ + "To visualize the effect of the 'BIOSCREEN'-formulation of source depletion, swap out some equation elements from the Anatrans model class.\n", + "\n", + "This is shown below. Make a custom class and initialize it with the same arguments. To inherrit the functionalities of Anatrans, assign it as the parent class by putting it between parentheses behind the class name. Then in the \\_\\_init\\_\\_, call the initialization of the parent class with super().\\_\\_init\\_\\_() and the class arguments. Then, the relevant portions of Anatrans can be adapted, while keeping all other functionalities the same." + ] + }, { "cell_type": "code", "execution_count": null, @@ -73,20 +85,11 @@ "metadata": {}, "outputs": [], "source": [ + "# Give the custom class a new name and make it inherit from Anatrans as a child class.\n", "class AnatransAlternativeSource(mbt.Anatrans):\n", - " \"\"\"Mibitrans model class with alternative calculations.\"\"\"\n", - "\n", - " def __init__(\n", - " self,\n", - " hydrological_parameters,\n", - " attenuation_parameters,\n", - " source_parameters,\n", - " model_parameters,\n", - " verbose=False,\n", - " ):\n", - " \"\"\"Initialize model class.\"\"\"\n", - " super().__init__(hydrological_parameters, attenuation_parameters, source_parameters, model_parameters, verbose)\n", - "\n", + " \"\"\"Mibitrans model class with alternative source depletion.\"\"\"\n", + " # Unless otherwise specified, all functionalities are now the same as Anatrans\n", + " # Change the equation terms to fit the alternative source depletion formulation.\n", " def _equation_term_source_depletion(self, xxx, ttt):\n", " term = np.exp(-self.k_source * (ttt - xxx / self.rv))\n", " # Term can be max 1; can not have 'generation' of solute ahead of advection.\n", @@ -109,47 +112,279 @@ "alt_object = AnatransAlternativeSource(hydro, att, source, model)\n", "alt_results = alt_object.run()\n", "\n", - "mbt_object = mbt.Mibitrans(hydro, att, source, model, verbose=True)\n", + "mbt_object = mbt.Mibitrans(hydro, att, source, model)\n", "mbt_results = mbt_object.run()\n" ] }, + { + "cell_type": "markdown", + "id": "43d7f0abc996155f", + "metadata": {}, + "source": [ + "There is a noticeable difference between the two implementations when the source mass is low (and therefore, $k_s$ is high). Over the model run time, these differences become more pronounced." + ] + }, { "cell_type": "code", "execution_count": null, - "id": "8f3caf71b2e44f2e", + "id": "7f6401bd8fb02806", "metadata": {}, "outputs": [], "source": [ - "%matplotlib ipympl" + "time = 365 * 10\n", + "alt_results.centerline(time = time, color=\"red\", linestyle=\"--\", label=\"anatrans alternative\")\n", + "ana_results.centerline(time = time, color=\"green\", label=\"anatrans\")\n", + "mbt_results.centerline(time = time, color=\"blue\", linestyle=\":\", label=\"mibitrans\")\n", + "plt.legend()\n", + "plt.show()" ] }, { "cell_type": "code", "execution_count": null, - "id": "7f6401bd8fb02806", + "id": "3020a36bdb0a98f5", + "metadata": {}, + "outputs": [], + "source": [ + "time = 365 * 10\n", + "alt_results.transverse(time = time, x_position=300, color=\"red\", linestyle=\"--\", label=\"anatrans alternative\")\n", + "ana_results.transverse(time = time, x_position=300, color=\"green\", label=\"anatrans\")\n", + "mbt_results.transverse(time = time, x_position=300, color=\"blue\", linestyle=\":\", label=\"mibitrans\")\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fb390d69b8b7ddc1", + "metadata": {}, + "outputs": [], + "source": [ + "def absolute_error(a, b):\n", + " \"\"\"Calculate the absolute error.\"\"\"\n", + " return b - a\n", + "def relative_error(a, b):\n", + " \"\"\"Calculate the relative error.\"\"\"\n", + " return (b - a) / a" + ] + }, + { + "cell_type": "markdown", + "id": "1afaad98b86fa5ff", + "metadata": {}, + "source": [ + "Visualizing the relative error of both formulations of source depletion shows that for Anatrans, the error with Mibitrans gradually changes of the plume length. However, for the alternative, there is peak in error at the advective front. In front and behind the advection, the alternative method resolves to the regular Anatrans results." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ecfe5b0e68228801", + "metadata": {}, + "outputs": [], + "source": [ + "diff_ana = relative_error(mbt_results.cxyt[:,len(ana_results.y)//2,:],\n", + " ana_results.cxyt[:,len(ana_results.y)//2,:])\n", + "diff_alt = relative_error(mbt_results.cxyt[:,len(ana_results.y)//2,:],\n", + " alt_results.cxyt[:,len(ana_results.y)//2,:])\n", + "y_plottable = [diff_ana[9,:], diff_ana[29,:], diff_ana[49,:],\n", + " diff_alt[9,:], diff_alt[29,:], diff_alt[49,:]]\n", + "color = [\"blue\", \"green\", \"red\", \"lightblue\", \"lightgreen\", \"tomato\"]\n", + "linestyle = [\"-\", \"-\", \"-\", \"--\", \"--\", \"--\"]\n", + "label = [\"anatrans, t=2y\", \"anatrans, t=6y\", \"anatrans, t=8y\",\n", + " \"alternative, t=2y\", \"alternative, t=6y\", \"alternative, t=8y\"]\n", + "\n", + "for i in range(len(y_plottable)):\n", + " plt.plot(ana_results.x,\n", + " y_plottable[i],\n", + " color=color[i],\n", + " linestyle=linestyle[i],\n", + " label=label[i]\n", + " )\n", + "plt.axhline(y=0, color='black', label=\"mibitrans\")\n", + "plt.legend()\n", + "plt.xlabel(\"x-position [m]\")\n", + "plt.ylabel(\"Relative error\")\n", + "plt.title(\"Relative error with Mibitrans along plume centerline\")\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9c625ecab3bec482", + "metadata": {}, + "outputs": [], + "source": [ + "lin_att = mbt.AttenuationParameters(\n", + " bulk_density=1.7, #[g/m3]\n", + " partition_coefficient=38, # [m^3/g]\n", + " fraction_organic_carbon=5.7e-5, # [-]\n", + " diffusion=0, # [m2/day]\n", + " half_life=2 * 365 # [days]\n", + ")\n", + "\n", + "ana_lin_object = mbt.Anatrans(hydro, lin_att, source, model)\n", + "ana_lin_results = ana_lin_object.run()\n", + "\n", + "alt_lin_object = AnatransAlternativeSource(hydro, lin_att, source, model)\n", + "alt_lin_results = alt_lin_object.run()\n", + "\n", + "mbt_lin_object = mbt.Mibitrans(hydro, lin_att, source, model)\n", + "mbt_lin_results = mbt_lin_object.run()" + ] + }, + { + "cell_type": "markdown", + "id": "524d40f20311c18e", + "metadata": {}, + "source": [ + "When also modelling linear decay, the plumes and their differences show similar behavior as before." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8c4ed92e2b48d0da", + "metadata": {}, + "outputs": [], + "source": [ + "time = 365 * 5\n", + "ana_lin_results.centerline(time = time, color=\"green\", label=\"anatrans\")\n", + "alt_lin_results.centerline(time = time, color=\"red\", linestyle=\"--\", label=\"anatrans alternative\")\n", + "mbt_lin_results.centerline(time = time, color=\"blue\", linestyle=\":\", label=\"mibitrans\")\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "39d0e8e689351364", "metadata": {}, "outputs": [], "source": [ - "plt.clf()\n", - "ana_results.centerline(color=\"green\", label=\"anatrans\")\n", - "alt_results.centerline(color=\"red\", linestyle=\"--\", label=\"anatrans alternative\")\n", - "mbt_results.centerline(color=\"blue\", linestyle=\":\", label=\"mibitrans\")\n", + "diff_ana = relative_error(mbt_lin_results.cxyt[:,len(ana_results.y)//2,:],\n", + " ana_lin_results.cxyt[:,len(ana_results.y)//2,:])\n", + "diff_alt = relative_error(mbt_lin_results.cxyt[:,len(ana_results.y)//2,:],\n", + " alt_lin_results.cxyt[:,len(ana_results.y)//2,:])\n", + "y_plottable = [diff_ana[9,:], diff_ana[29,:], diff_ana[49,:],\n", + " diff_alt[9,:], diff_alt[29,:], diff_alt[49,:]]\n", + "color = [\"blue\", \"green\", \"red\", \"lightblue\", \"lightgreen\", \"tomato\"]\n", + "linestyle = [\"-\", \"-\", \"-\", \"--\", \"--\", \"--\"]\n", + "label = [\"anatrans, t=2y\", \"anatrans, t=6y\", \"anatrans, t=8y\",\n", + " \"alternative, t=2y\", \"alternative, t=6y\", \"alternative, t=8y\"]\n", + "\n", + "for i in range(len(y_plottable)):\n", + " plt.plot(ana_results.x,\n", + " y_plottable[i],\n", + " color=color[i],\n", + " linestyle=linestyle[i],\n", + " label=label[i]\n", + " )\n", + "\n", + "plt.axhline(y=0, color='black', label=\"mibitrans\")\n", "plt.legend()\n", + "plt.xlabel(\"x-position [m]\")\n", + "plt.ylabel(\"Relative error\")\n", + "plt.title(\"Relative error with Mibitrans along plume centerline\")\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": null, - "id": "ba19cf9692813b01", + "id": "40d94b4c9e00b281", "metadata": {}, "outputs": [], "source": [ - "plt.clf()\n", - "anim = mbt.centerline(model=[ana_results, alt_results, mbt_results],\n", - " legend_names=[\"Anatrans model\", \"Alternative model\", \"mibitrans model\"],\n", - " linestyle=\":\",\n", - " animate=True)\n", + "large_source = mbt.SourceParameters(\n", + " source_zone_boundary=np.array([30/ft, 35/ft, 40/ft]), # [m]\n", + " source_zone_concentration=np.array([13.68, 2.508, 0.057]), # [g/m3]\n", + " depth=10/ft, # [m]\n", + " total_mass=500000 # [g]\n", + ")\n", + "\n", + "ana_large_object = mbt.Anatrans(hydro, att, large_source, model)\n", + "ana_large_results = ana_large_object.run()\n", + "\n", + "alt_large_object = AnatransAlternativeSource(hydro, att, large_source, model)\n", + "alt_large_results = alt_large_object.run()\n", + "\n", + "mbt_large_object = mbt.Mibitrans(hydro, att, large_source, model)\n", + "mbt_large_results = mbt_large_object.run()\n" + ] + }, + { + "cell_type": "markdown", + "id": "e1f291ad2f3e90e4", + "metadata": {}, + "source": [ + "When increasing source mass, differences between two source depletion formulations become less apparent. Arguably, the alternative formulation comes closer to the exact Mibitrans solution than Anatrans does." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25629ba2d67e3cc7", + "metadata": {}, + "outputs": [], + "source": [ + "time = 365*10\n", + "ana_large_results.centerline(time = time, color=\"green\", label=\"anatrans\")\n", + "alt_large_results.centerline(time = time, color=\"red\", linestyle=\"--\", label=\"anatrans alternative\")\n", + "mbt_large_results.centerline(time = time, color=\"blue\", linestyle=\":\", label=\"mibitrans\")\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1327e66cd2f48442", + "metadata": {}, + "outputs": [], + "source": [ + "time = 365*10\n", + "ana_large_results.transverse(time = time, x_position=150, color=\"green\", label=\"anatrans\")\n", + "alt_large_results.transverse(time = time, x_position=150, color=\"red\", linestyle=\"--\", label=\"anatrans alternative\")\n", + "mbt_large_results.transverse(time = time, x_position=150, color=\"blue\", linestyle=\":\", label=\"mibitrans\")\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7f34316918ad378f", + "metadata": {}, + "outputs": [], + "source": [ + "# The absolute error does a better job visualizing the differences here\n", + "diff_ana = absolute_error(mbt_large_results.cxyt[:,len(ana_results.y)//2,:],\n", + " ana_large_results.cxyt[:,len(ana_results.y)//2,:])\n", + "diff_alt = absolute_error(mbt_large_results.cxyt[:,len(ana_results.y)//2,:],\n", + " alt_large_results.cxyt[:,len(ana_results.y)//2,:])\n", + "y_plottable = [diff_ana[9,:], diff_ana[29,:], diff_ana[49,:],\n", + " diff_alt[9,:], diff_alt[29,:], diff_alt[49,:]]\n", + "color = [\"blue\", \"green\", \"red\", \"lightblue\", \"lightgreen\", \"tomato\"]\n", + "linestyle = [\"-\", \"-\", \"-\", \"--\", \"--\", \"--\"]\n", + "label = [\"anatrans, t=2y\", \"anatrans, t=6y\", \"anatrans, t=8y\",\n", + " \"alternative, t=2y\", \"alternative, t=6y\", \"alternative, t=8y\"]\n", + "\n", + "for i in range(len(y_plottable)):\n", + " plt.plot(ana_results.x,\n", + " y_plottable[i],\n", + " color=color[i],\n", + " linestyle=linestyle[i],\n", + " label=label[i]\n", + " )\n", + "plt.axhline(y=0, color='black', label=\"mibitrans\")\n", + "plt.legend()\n", + "plt.xlabel(\"x-position [m]\")\n", + "plt.ylabel(\"Absolute error [g/m3]\")\n", + "plt.title(\"Absolute error with Mibitrans along plume centerline\")\n", "plt.show()" ] } From 98ad22defea705e784b43db3bbd64eb4821cbe8d Mon Sep 17 00:00:00 2001 From: Bakker Date: Thu, 12 Feb 2026 14:03:08 +0100 Subject: [PATCH 17/19] Made notebook exploring t=x/v substitution --- examples/example_validity_substitution.ipynb | 248 +++++++++++++------ 1 file changed, 168 insertions(+), 80 deletions(-) diff --git a/examples/example_validity_substitution.ipynb b/examples/example_validity_substitution.ipynb index 7011dd9..34851ec 100644 --- a/examples/example_validity_substitution.ipynb +++ b/examples/example_validity_substitution.ipynb @@ -13,6 +13,62 @@ "import mibitrans as mbt\n" ] }, + { + "cell_type": "markdown", + "id": "e50f93803d773894", + "metadata": {}, + "source": [ + "The governing equation for advective-dispersive transport with adsorption and decay given by Bear (1979) is:\n", + "\n", + "\\begin{equation}\\tag{1}\n", + " R\\frac{\\partial C}{\\partial t} = -v\\frac{\\partial C}{\\partial x} + D_{x}\\frac{\\partial ^2 C}{\\partial x^2} + D_y \\frac{\\partial^2 C}{\\partial y^2} + D_z \\frac{\\partial^2 C}{\\partial z^2} - \\mu C\n", + "\\end{equation}\n", + "\n", + "With $0\\leq x \\leq \\infty$, $-\\infty \\leq y \\leq \\infty$ and $-\\infty \\leq z \\leq \\infty$. With the contaminant source modeled as a continuous planar block located at x=0 and of width Y and thickness Z, one gets the following exact analytical equation given by Wexler (1992):\n", + "\n", + "\\begin{align}\\tag{2}\n", + " C(x,y,z,t) = & \\frac{C_{0}x}{8\\sqrt{\\pi \\alpha_x v/R}}\n", + " \\int_0^t \\tau^{-3/2} \\exp \\left(-\\mu\\tau - \\frac{(x-v\\tau/R)^2}{4\\alpha_xv\\tau/R}\\right) \\cdot\\\\\n", + " & \\quad \\left[\\operatorname{erf} \\left( \\frac{y+Y/2}{2\\sqrt{D_y\\tau/R}} \\right) -\n", + " \\operatorname{erf} \\left( \\frac{y-Y/2}{2\\sqrt{D_y\\tau/R}} \\right)\\right]\\cdot \\nonumber \\\\\n", + " & \\quad\\left[\\operatorname{erf} \\left( \\frac{z+Z/2}{2\\sqrt{D_z\\tau/R}} \\right) -\n", + " \\operatorname{erf} \\left( \\frac{z-Z/2}{2\\sqrt{D_z\\tau/R}} \\right)\\right] d\\tau\n", + " \\nonumber\n", + "\\end{align}\n", + "\n", + "Where $\\tau$ is the integration variable for time. There is however, no full exact analytical solution of this equation.\n", + "\n", + "The paper of Domenico and Robbins (1985) suggests an approximation to the 3D solution by using the product of the 1D solution in each direction:\n", + "\n", + "\\begin{equation}\\tag{3}\n", + " C(x,y,z,t) \\approx C(x,t) \\cdot C(y,t) \\cdot C(z,t)\n", + "\\end{equation}\n", + "\n", + "Individually, each 1D solution can be derived in a full analytical equation, leading to:\n", + "\n", + "\\begin{align}\\tag{4}\n", + " C(x, y, z, t) &= \\frac{C_{0}}{8} \\left( \\exp \\left[ \\frac{xv\\left(1-\\sqrt{1+4\\mu R D_x/v^2}\\right)}{2D_x}\\right] \\right. \\\\\n", + " &\\quad \\quad \\cdot \\operatorname{erfc} \\left[ \\frac{x - \\frac{vt}{R}\\sqrt{1+4\\mu R D_x/v^2}}{2\\sqrt{D_x t / R }} \\right] \\\\\n", + " &\\quad \\ \\, + \\exp \\left[ \\frac{xv\\left(1+\\sqrt{1+4\\mu R D_x/v^2}\\right)}{2D_x}\\right] \\\\\n", + " &\\quad \\quad \\cdot \\left. \\operatorname{erfc} \\left[ \\frac{x + \\frac{vt}{R}\\sqrt{1+4\\mu R D_x/v^2}}{2\\sqrt{D_x t/R }} \\right] \\right) \\\\\n", + " &\\quad \\cdot \\left( \\operatorname{erf} \\left[ \\frac{y + Y/2}{2\\sqrt{D_y t / R}} \\right] - \\operatorname{erf} \\left[ \\frac{y - Y/2}{2\\sqrt{D_y t / R)}} \\right] \\right) \\\\\n", + " &\\quad \\cdot \\left( \\operatorname{erf} \\left[ \\frac{z + Z/2}{2\\sqrt{D_z t / R)}} \\right] - \\operatorname{erf} \\left[ \\frac{z - Z/2}{2\\sqrt{D_z t / R}} \\right] \\right)\n", + "\\end{align}\n", + "\n", + "However, the equation presented in Domenico (1987) is different:\n", + "\n", + "\\begin{align}\\tag{5}\n", + " C(x, y, z, t) &= \\frac{C_{0}}{8} \\left( \\exp \\left[ \\frac{xv\\left(1-\\sqrt{1+4\\mu R D_x/v^2}\\right)}{2D_x}\\right] \\right. \\\\\n", + " &\\quad \\quad \\cdot \\operatorname{erfc} \\left[ \\frac{x - \\frac{vt}{R}\\sqrt{1+4\\mu R D_x/v^2}}{2\\sqrt{D_x t / R }} \\right] \\\\\n", + " &\\quad \\cdot \\left( \\operatorname{erf} \\left[ \\frac{y + Y/2}{2\\sqrt{\\alpha_y x}} \\right] - \\operatorname{erf} \\left[ \\frac{y - Y/2}{2\\sqrt{\\alpha_y x)}} \\right] \\right) \\\\\n", + " &\\quad \\cdot \\left( \\operatorname{erf} \\left[ \\frac{z + Z/2}{2\\sqrt{\\alpha_z x)}} \\right] - \\operatorname{erf} \\left[ \\frac{z - Z/2}{2\\sqrt{\\alpha_z x}} \\right] \\right)\n", + "\\end{align}\n", + "\n", + "Notice the missing second exponent and erfc term of the $C(x,t)$ solution, and that in the transverse dispersion terms, $D_{y,z}t/R$ is replaced by $\\alpha_{y,z}x$. The first difference is due to a truncation of the x-term in the solution. But equation \\tag{5} can be untruncated and include the additional x-term without any issue, and is implemented as such in the Anatrans solution of this package. The second difference is accomplished by substituting $t$ for $x/v$ (technically $xR/v$ since we consider retardation). Assuming the effect of molecular diffusion in transport is negligible, $D_{y,z} = \\alpha_{y,z}v$. Therefore making $2\\sqrt{D_{y,z} t / R)} = 2\\sqrt{\\alpha_{y,z}v\\frac{xR}{v}/R} = 2\\sqrt{\\alpha_{y,z}x}$. $x/v$ can be seen as the time it takes for the advective front to reach position $x$ under flow velocity $v$.\n", + "\n", + "Albeit this substitution in equation 4 lacks a mathematical basis, as put in West et al. (2007), it is essential for approximating the exact equation. For a continuous source, $\\lim_{t \\to \\infty}C(x=0,y,z,t)$ should be $C_0$, absent any source decay or depletion. However, for equation 4, $\\lim_{t \\to \\infty}C(x=0,y,z,t) = 0$, as both transverse dispersion terms go to 0 for $t \\to \\infty$. Therefore $C(x,y,z,t) \\not \\approx C(x,t) \\cdot C(y,t) \\cdot C(z,t)$, meaning equation 4 (presented as equation 22 in West et al. (2007) as the 'final approximate nontruncated solution') is not a correct approximate solution in the first place. This is shown below by comparing equation 4 and 5 to the exact solution, equation 2. To more accurately compare the two equations, equation 5 is used in its untruncated form." + ] + }, { "cell_type": "code", "execution_count": null, @@ -20,63 +76,40 @@ "metadata": {}, "outputs": [], "source": [ - "ft = 3.281\n", + "\n", "hydro = mbt.HydrologicalParameters(\n", - " velocity=350/ft/365,\n", - " h_gradient=0.048, # Hydraulic gradient [-]\n", - " h_conductivity=0.495, # Hydraulic conductivity [m/day]\n", - " porosity=0.25, # Effective soil porosity [-]\n", - " alpha_x=13.3/ft/5, # Longitudinal dispersivity, in [m]\n", - " alpha_y=1.3/ft/5, # Transverse horizontal dispersivity, in [m]\n", - " alpha_z=0 # Transverse vertical dispersivity, in [m]\n", + " velocity=0.3, #[m/d]\n", + " porosity=0.25, #[-]\n", + " alpha_x=4, #[m]\n", + " alpha_y=0.4, #[m]\n", + " alpha_z=0.04 #[m]\n", ")\n", "\n", "att = mbt.AttenuationParameters(\n", - " # Soil bulk density in [g/m^3]\n", - " bulk_density=1.7,\n", - " # Partition coefficient of the transported contaminant to soil organic matter, in [m^3/g]\n", - " partition_coefficient=38,\n", - " # Fraction of organic material in the soil [-]\n", - " fraction_organic_carbon=5.7e-5,\n", - " # Molecular diffusion, in [m2/day]\n", - " diffusion=0,\n", - " # Contaminant half life, in [days]\n", - " half_life=0\n", + " bulk_density=1.7, #[g/m^3]\n", + " partition_coefficient=38, #[m^3/g]\n", + " fraction_organic_carbon=5.7e-5, #[-]\n", + " diffusion=0, #[m2/day]\n", + " half_life=0 #[days]\n", ")\n", "\n", "source = mbt.SourceParameters(\n", - " source_zone_boundary=np.array([30/ft, 35/ft, 40/ft]),\n", - " source_zone_concentration=np.array([13.68, 2.508, 0.057]),\n", - " depth=10/ft,\n", + " source_zone_boundary=np.array([11]),\n", + " source_zone_concentration=np.array([14]),\n", + " depth=3,\n", " total_mass=\"inf\"\n", ")\n", "\n", "model = mbt.ModelParameters(\n", - " # Model extent in the longitudinal (x) direction in [m].\n", - " model_length = 1000/ft,\n", - " # Model extent in the transverse horizontal (y) direction in [m].\n", - " model_width = 300/ft,\n", - " # Model duration in [days].\n", - " model_time = 10 * 365,\n", - " # Model grid discretization step size in the longitudinal (x) direction, in [m].\n", - " dx = 2/ft,\n", - " # Model grid discretization step size in the transverse horizontal (y) direction, in [m].\n", - " dy = 1/ft,\n", - " # Model time discretization step size, in [days]\n", - " dt = 25\n", + " model_length = 450, #[m]\n", + " model_width = 100, #[m]\n", + " model_time = 4 * 365, #[days]\n", + " dx = 1, #[m]\n", + " dy = 1, #[m]\n", + " dt = 25 #[days]\n", ")" ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "4ce54f3015742952", - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib ipympl" - ] - }, { "cell_type": "code", "execution_count": null, @@ -84,19 +117,8 @@ "metadata": {}, "outputs": [], "source": [ - "class AnatransAlternativeDispersion(mbt.Anatrans):\n", - " \"\"\"Mibitrans model class with alternative calculations.\"\"\"\n", - "\n", - " def __init__(\n", - " self,\n", - " hydrological_parameters,\n", - " attenuation_parameters,\n", - " source_parameters,\n", - " model_parameters,\n", - " verbose=False,\n", - " ):\n", - " \"\"\"Initialize model class.\"\"\"\n", - " super().__init__(hydrological_parameters, attenuation_parameters, source_parameters, model_parameters, verbose)\n", + "class AnatransWithoutSubstitution(mbt.Anatrans):\n", + " \"\"\"Anatrans model class without substitution.\"\"\"\n", "\n", " def _equation_term_z(self, ttt):\n", " inner_term = self._src_pars.depth / (2 * np.sqrt(self._hyd_pars.alpha_z * self.rv * ttt))\n", @@ -110,12 +132,11 @@ "\n", " def _calculate_concentration_for_all_xyt(self, xxx, yyy, ttt):\n", " cxyt = 0\n", - " #adapted decay_sqrt\n", " decay_sqrt = np.sqrt(1 + 4 * (self._decay_rate - self.k_source) * self._hyd_pars.alpha_x / self.rv)\n", " x_term = self._equation_term_x(xxx, ttt, decay_sqrt)\n", " additional_x = self._equation_term_additional_x(xxx, ttt, decay_sqrt)\n", " z_term = self._equation_term_z(ttt)\n", - " source_decay = self._equation_term_source_decay(xxx, ttt)\n", + " source_decay = self._equation_term_source_depletion(xxx, ttt)\n", " for i in range(len(self.c_source)):\n", " y_term = self._equation_term_y(i, yyy,ttt)\n", " cxyt_step = 1 / 8 * self.c_source[i] * source_decay * (x_term + additional_x) * y_term * z_term\n", @@ -137,62 +158,129 @@ "source": [ "ana_object = mbt.Anatrans(hydro, att, source, model)\n", "ana_results = ana_object.run()\n", - "alt_disp_object = AnatransAlternativeDispersion(hydro, att, source, model)\n", - "alt_disp_results = alt_disp_object.run()" + "alt_disp_object = AnatransWithoutSubstitution(hydro, att, source, model)\n", + "alt_disp_results = alt_disp_object.run()\n", + "mbt_object = mbt.Mibitrans(hydro, att, source, model)\n", + "mbt_results = mbt_object.run()" ] }, { "cell_type": "code", "execution_count": null, - "id": "4deae7fe281c14fb", + "id": "d96482cdc4854c1e", "metadata": {}, "outputs": [], "source": [ - "plt.clf()\n", - "ana_results.centerline(color=\"green\")\n", - "alt_disp_results.centerline(color=\"blue\", linestyle=\"-.\")\n", + "mbt_results.centerline(time=182, color=\"green\", label=\"eq2, t=182days\")\n", + "mbt_results.centerline(time=365, color=\"blue\", label=\"eq2, t=365days\")\n", + "mbt_results.centerline(time=3*365, color=\"red\", label=\"eq2, t=1095days\")\n", + "ana_results.centerline(time=182, color=\"limegreen\", linestyle=\"-.\", label=\"eq5, t=182days\")\n", + "ana_results.centerline(time=365, color=\"cornflowerblue\", linestyle=\"-.\", label=\"eq5, t=365days\")\n", + "ana_results.centerline(time=3*365, color=\"orangered\", linestyle=\"-.\", label=\"eq5, t=1095days\")\n", + "alt_disp_results.centerline(time=182, color=\"lightgreen\", linestyle=\":\", label=\"eq4, t=182days\")\n", + "alt_disp_results.centerline(time=365, color=\"lightblue\", linestyle=\":\", label=\"eq4, t=365days\")\n", + "alt_disp_results.centerline(time=3*365, color=\"tomato\", linestyle=\":\", label=\"eq4, t=1095days\")\n", + "\n", + "plt.title(\"Centerline plot of various models\")\n", + "plt.legend()\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": null, - "id": "d96482cdc4854c1e", + "id": "cb5f171cbfb23c90", "metadata": {}, "outputs": [], "source": [ - "plt.clf()\n", - "anim = mbt.centerline(model=[ana_results, alt_disp_results],\n", - " legend_names=[\"Anatrans model\", \"Alt disp model\"],\n", - " linestyle=\":\",\n", - " animate=True)\n", + "mbt_results.transverse(time=182, x_position=75, color=\"green\", label=\"eq2, t=182days\")\n", + "mbt_results.transverse(time=365, x_position=75, color=\"blue\", label=\"eq2, t=365days\")\n", + "mbt_results.transverse(time=3*365, x_position=75, color=\"red\", label=\"eq2, t=1095days\")\n", + "ana_results.transverse(time=182, x_position=75, color=\"limegreen\", linestyle=\"-.\", label=\"ana, t=182days\")\n", + "ana_results.transverse(time=365, x_position=75, color=\"cornflowerblue\", linestyle=\"-.\", label=\"ana, t=365days\")\n", + "ana_results.transverse(time=3*365, x_position=75, color=\"orangered\", linestyle=\"-.\", label=\"ana, t=1095days\")\n", + "alt_disp_results.transverse(time=182, x_position=75, color=\"lightgreen\", linestyle=\":\", label=\"eq4, t=182days\")\n", + "alt_disp_results.transverse(time=365, x_position=75, color=\"lightblue\", linestyle=\":\", label=\"eq4, t=365days\")\n", + "alt_disp_results.transverse(time=3*365, x_position=75, color=\"tomato\", linestyle=\":\", label=\"eq4, t=1095days\")\n", + "plt.ylim(0, 10)\n", + "plt.title(\"Transverse plot of various models, at x=75m\")\n", + "plt.legend()\n", "plt.show()" ] }, + { + "cell_type": "markdown", + "id": "d91bbb7193af3400", + "metadata": {}, + "source": [ + "As these graphs show, equation 4, without substitution, does not come close to the exact solution of equation 2 for larger times. Equation 5, with substitution has a small error with the exact solution, but follows the same general trend. As equation 4 does not correspond with the boundary condition $\\lim_{t \\to \\infty}C(x=0,y,z,t) = C_0$, source concentrations do not stay constant. The expressions for transverse dispersion in equation 4 do not involve any representation of x-position and therefore can not abide to a boundary condition at x=0." + ] + }, + { + "cell_type": "markdown", + "id": "9d7f83bc24f06990", + "metadata": {}, + "source": [ + "Instead of equation 3, the solution from Domenico (1987), is more akin to $C(x,y,z,t) \\approx C(x,t) \\cdot C(x,y) \\cdot (x,z)$. But these solutions can not be mathematically derived from the governing equations. Therefore derivation of the Domenico solution is best represented by starting at equation 2. Assume that transverse dispersion is not dependent on time, but on position along the flow direction. Diffusion is removed from the dispersion coefficient, since it is independent from flow velocity and position. The time integration variable $\\tau$ therefore is converted into $\\frac{xR}{v}$, resolving to:\n", + "\\begin{align}\\tag{6}\n", + " C(x,y,z,t) = & \\frac{C_{0}x}{8\\sqrt{\\pi \\alpha_x v/R}}\n", + " \\int_0^t \\tau^{-3/2} \\exp \\left(-\\mu\\tau - \\frac{(x-v\\tau/R)^2}{4\\alpha_xv\\tau/R}\\right) \\cdot\\\\\n", + " & \\quad \\left[\\operatorname{erf} \\left( \\frac{y+Y/2}{2\\sqrt{\\alpha_y x}} \\right) -\n", + " \\operatorname{erf} \\left( \\frac{y-Y/2}{2\\sqrt{\\alpha_y x}} \\right)\\right]\\cdot \\nonumber \\\\\n", + " & \\quad\\left[\\operatorname{erf} \\left( \\frac{z+Z/2}{2\\sqrt{\\alpha_z x}} \\right) -\n", + " \\operatorname{erf} \\left( \\frac{z-Z/2}{2\\sqrt{\\alpha_z x}} \\right)\\right] d\\tau\n", + " \\nonumber\n", + "\\end{align}\n", + "\n", + "Now independent of time, the transverse dispersion terms can be taken outside of the integral. Resulting in:\n", + "\n", + "\\begin{align}\\tag{4}\n", + " C(x, y, z, t) &= \\frac{C_{0}}{8} \\left( \\exp \\left[ \\frac{xv\\left(1-\\sqrt{1+4\\mu R D_x/v^2}\\right)}{2D_x}\\right] \\right. \\\\\n", + " &\\quad \\quad \\cdot \\operatorname{erfc} \\left[ \\frac{x - \\frac{vt}{R}\\sqrt{1+4\\mu R D_x/v^2}}{2\\sqrt{D_x t / R }} \\right] \\\\\n", + " &\\quad \\ \\, + \\exp \\left[ \\frac{xv\\left(1+\\sqrt{1+4\\mu R D_x/v^2}\\right)}{2D_x}\\right] \\\\\n", + " &\\quad \\quad \\cdot \\left. \\operatorname{erfc} \\left[ \\frac{x + \\frac{vt}{R}\\sqrt{1+4\\mu R D_x/v^2}}{2\\sqrt{D_x t/R }} \\right] \\right) \\\\\n", + " &\\quad \\cdot \\left( \\operatorname{erf} \\left[ \\frac{y + Y/2}{2\\sqrt{\\alpha_y x}} \\right] - \\operatorname{erf} \\left[ \\frac{y - Y/2}{2\\sqrt{\\alpha_y x}} \\right] \\right) \\\\\n", + " &\\quad \\cdot \\left( \\operatorname{erf} \\left[ \\frac{z + Z/2}{2\\sqrt{\\alpha_z x}} \\right] - \\operatorname{erf} \\left[ \\frac{z - Z/2}{2\\sqrt{\\alpha_z x}} \\right] \\right)\n", + "\\end{align}" + ] + }, + { + "cell_type": "markdown", + "id": "74362679a9d1935c", + "metadata": {}, + "source": [ + "It should be noted that equation 5 also does not respect the boundary condition $\\lim_{t \\to \\infty}C(x=0,y,z,t) = C_0$ for smaller times due to the truncation, see the example below:" + ] + }, { "cell_type": "code", "execution_count": null, - "id": "b2948327fd1a2e3f", + "id": "91b08c061186d19f", "metadata": {}, "outputs": [], "source": [ - "plt.clf()\n", - "anim = mbt.plume_3d(model=alt_disp_results,\n", - " animate=True,\n", - " cmap='viridis')\n", - "plt.show()" + "bio_object = mbt.Bioscreen(hydro, att, source, model)\n", + "bio_results = bio_object.run()" ] }, { "cell_type": "code", "execution_count": null, - "id": "355d03639545ee93", + "id": "7b727beb694d3b4a", "metadata": {}, "outputs": [], "source": [ - "anim = ana_results.plume_3d(\n", - " animate=True,\n", - " cmap='viridis')\n", + "plot_times = [25,50,100,200]\n", + "mbt_color = [\"blue\", \"deepskyblue\", \"lime\", \"yellowgreen\"]\n", + "bio_color = [\"lightblue\", \"skyblue\", \"lightgreen\", \"lawngreen\"]\n", + "for i in range(len(plot_times)):\n", + " mbt_results.centerline(time=plot_times[i], color = mbt_color[i],\n", + " label=f\"eq 2, t={plot_times[i]} days\")\n", + " bio_results.centerline(time=plot_times[i], linestyle = \"-.\", color = bio_color[i],\n", + " label=f\"eq 5, t={plot_times[i]} days\")\n", + "plt.legend()\n", + "plt.xlim(-10,125)\n", + "plt.title(\"Difference in source concentration for different transport equations\")\n", "plt.show()" ] } From 83c9925ed9834fc7cc516694d707a353ab39e9d8 Mon Sep 17 00:00:00 2001 From: Bakker Date: Fri, 13 Feb 2026 10:52:26 +0100 Subject: [PATCH 18/19] Updated notebook with comparison to BIOSCREEN Notebook compares output from mibitrans models with the models from BIOSCREEN and BIOSCREEN-AT for proof of functionality. --- examples/example_BIOSCREEN_comparison.ipynb | 417 ++++++++++++++++++++ examples/example_exact_equation.ipynb | 373 ----------------- examples/example_keesler.ipynb | 1 - 3 files changed, 417 insertions(+), 374 deletions(-) create mode 100644 examples/example_BIOSCREEN_comparison.ipynb delete mode 100644 examples/example_exact_equation.ipynb diff --git a/examples/example_BIOSCREEN_comparison.ipynb b/examples/example_BIOSCREEN_comparison.ipynb new file mode 100644 index 0000000..6bf6e17 --- /dev/null +++ b/examples/example_BIOSCREEN_comparison.ipynb @@ -0,0 +1,417 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "2231e3885ab4c960", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import mibitrans as mbt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1c04100008cf2f67", + "metadata": {}, + "outputs": [], + "source": [ + "ft = 3.281 # factor to convert ft to m" + ] + }, + { + "cell_type": "markdown", + "id": "642abbcc74f16426", + "metadata": {}, + "source": [ + "This notebook compares the examples present in BIOSCREEN and BIOSCREEN-AT with the similar models (Bioscreen/Anatrans and Mibitrans respectively) in the mibitrans package." + ] + }, + { + "cell_type": "markdown", + "id": "8efc6ab254154e63", + "metadata": {}, + "source": [ + "### BIOSCREEN example dataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c855595dbf62b868", + "metadata": {}, + "outputs": [], + "source": [ + "bio_hydro = mbt.HydrologicalParameters(\n", + " velocity=1609.1 / ft / 365, #[m/d]\n", + " porosity=0.25, #[-]\n", + " alpha_x=28.5 / ft, #[m]\n", + " alpha_y=2.9 / ft, #[m]\n", + " alpha_z=0 / ft, #[m]\n", + ")\n", + "\n", + "bio_att = mbt.AttenuationParameters(\n", + " bulk_density=1700, #[kg/m3]\n", + " partition_coefficient=0.038, #[m3/kg]\n", + " fraction_organic_carbon=0.0008, #[-]\n", + " half_life=0, #[d]\n", + ")\n", + "\n", + "bio_source = mbt.SourceParameters(\n", + " source_zone_boundary=np.array([50, 75, 125]) / ft, #[m]\n", + " source_zone_concentration=np.array([9, 2.8, 0.07]), #[g/m3]\n", + " depth=10 / ft, #[m]\n", + " total_mass=\"inf\", #[g]\n", + ")\n", + "\n", + "bio_model = mbt.ModelParameters(\n", + " model_length=1500 / ft, #[m]\n", + " model_width=400 / ft, #[m]\n", + " model_time=5 * 365, #[d]\n", + " dx=5 / ft, #[m]\n", + " dy=5 / ft, #[m]\n", + " dt=365/2, #[d]\n", + ")\n", + "\n", + "bio_ea = mbt.ElectronAcceptors(\n", + " delta_oxygen=5.78, #[g/m3]\n", + " delta_nitrate=17, #[g/m3]\n", + " ferrous_iron=11.3, #[g/m3]\n", + " delta_sulfate=100, #[g/m3]\n", + " methane=0.414, #[g/m3]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aaf753b5640b909c", + "metadata": {}, + "outputs": [], + "source": [ + "#bio_bio; BIOSCREEN input with Bioscreen model\n", + "bio_bio_object = mbt.Bioscreen(bio_hydro, bio_att, bio_source, bio_model)\n", + "#nd; no decay\n", + "bio_bio_nd_results = bio_bio_object.run()\n", + "\n", + "bio_bio_object.attenuation_parameters.half_life = 0.1 * 365 #[d]\n", + "#dc; decay\n", + "bio_bio_dc_results = bio_bio_object.run()\n", + "\n", + "bio_bio_object.instant_reaction(bio_ea)\n", + "#inst; instant reaction\n", + "bio_bio_inst_results = bio_bio_object.run()\n", + "\n", + "#bio_ana; BIOSCREEN input with Anatrans model\n", + "bio_ana_object = mbt.Anatrans(bio_hydro, bio_att, bio_source, bio_model)\n", + "bio_ana_nd_results = bio_ana_object.run()\n", + "\n", + "bio_ana_object.attenuation_parameters.half_life = 0.1 * 365 #[d]\n", + "bio_ana_dc_results = bio_ana_object.run()\n", + "\n", + "bio_ana_object.instant_reaction(bio_ea)\n", + "bio_ana_inst_results = bio_ana_object.run()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e997f41098b2cb2", + "metadata": {}, + "outputs": [], + "source": [ + "time_bio = [365*0.5, 365, 2*365, 5*365] #[d]\n", + "BIOSCREEN_data_nodecay = np.array([\n", + " [9.000, 8.435, 7.267, 5.900, 4.091, 2.151, 0.780, 0.183, 0.027, 0.002, 0.000], # 0.5y\n", + " [9.000, 8.467, 7.465, 6.680, 6.070, 5.548, 5.006, 4.327, 3.441, 2.410, 1.432], # 1y\n", + " [9.000, 8.467, 7.466, 6.684, 6.089, 5.624, 5.250, 4.940, 4.679, 4.454, 4.256], # 2y\n", + " [9.000, 8.467, 7.466, 6.684, 6.089, 5.624, 5.250, 4.940, 4.679, 4.455, 4.260] # 5y\n", + "])\n", + "\n", + "BIOSCREEN_data_lineardecay = np.array([\n", + " [9.000, 4.348, 1.965, 0.889, 0.386, 0.146, 0.043, 0.009, 0.001, 0.000, 0.000],\n", + " [9.000, 4.348, 1.969, 0.905, 0.424, 0.201, 0.096, 0.046, 0.022, 0.010, 0.004],\n", + " [9.000, 4.348, 1.969, 0.905, 0.424, 0.201, 0.096, 0.047, 0.023, 0.011, 0.005],\n", + " [9.000, 4.348, 1.969, 0.905, 0.424, 0.201, 0.096, 0.047, 0.023, 0.011, 0.005]\n", + "])\n", + "\n", + "BIOSCREEN_data_instant = np.array([\n", + " [9.000, 8.332, 6.473, 2.364, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000],\n", + " [9.000, 8.466, 7.405, 6.331, 5.165, 3.757, 1.724, 0.000, 0.000, 0.000, 0.000],\n", + " [9.000, 8.466, 7.407, 6.350, 5.268, 4.192, 3.152, 2.167, 1.244, 0.378, 0.000],\n", + " [9.000, 8.466, 7.407, 6.350, 5.268, 4.192, 3.152, 2.168, 1.245, 0.385, 0.000]\n", + "])\n", + "\n", + "bio_x = np.array([0, 145, 290, 435, 580, 725, 870, 1015, 1160, 1305, 1450]) / ft #[m]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d75411d12b26a2a0", + "metadata": {}, + "outputs": [], + "source": [ + "colors = [\"tomato\", \"sandybrown\", \"khaki\", \"greenyellow\", \"lightgreen\"]\n", + "colors_bio = [\"red\", \"orange\", \"gold\", \"yellowgreen\", \"green\"]\n", + "for i in range(len(time_bio)):\n", + " mbt.centerline(bio_bio_nd_results, time=time_bio[i], color=colors[i], label=f\"Bioscreen, t={time_bio[i]}d\")\n", + " plt.plot(bio_x, BIOSCREEN_data_nodecay[i, :], color=colors_bio[i],\n", + " linestyle=\":\", label=f\"BIOSCREEN, t={time_bio[i]}d\")\n", + "plt.title(\"Comparison of BIOSCREEN and Bioscreen models without decay\")\n", + "plt.legend()\n", + "plt.show()\n", + "\n", + "for i in range(len(time_bio)):\n", + " mbt.centerline(bio_bio_dc_results, time=time_bio[i], color=colors[i], label=f\"Bioscreen, t={time_bio[i]}d\")\n", + " plt.plot(bio_x, BIOSCREEN_data_lineardecay[i, :], color=colors_bio[i],\n", + " linestyle=\":\", label=f\"BIOSCREEN, t={time_bio[i]}d\")\n", + "plt.title(\"Comparison of BIOSCREEN and Bioscreen models with linear decay\")\n", + "plt.legend()\n", + "plt.show()\n", + "\n", + "for i in range(len(time_bio)):\n", + " mbt.centerline(bio_bio_inst_results, time=time_bio[i], color=colors[i], label=f\"Bioscreen, t={time_bio[i]}d\")\n", + " plt.plot(bio_x, BIOSCREEN_data_instant[i, :], color=colors_bio[i],\n", + " linestyle=\":\", label=f\"BIOSCREEN, t={time_bio[i]}d\")\n", + "plt.title(\"Comparison of BIOSCREEN and Bioscreen models with instant reaction\")\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9feacaadcdce7de4", + "metadata": {}, + "outputs": [], + "source": [ + "colors = [\"tomato\", \"sandybrown\", \"khaki\", \"greenyellow\", \"lightgreen\"]\n", + "colors_bio = [\"red\", \"orange\", \"gold\", \"yellowgreen\", \"green\"]\n", + "for i in range(len(time_bio)):\n", + " mbt.centerline(bio_ana_nd_results, time=time_bio[i], color=colors[i], label=f\"Anatrans, t={time_bio[i]}d\")\n", + " plt.plot(bio_x, BIOSCREEN_data_nodecay[i, :], color=colors_bio[i],\n", + " linestyle=\":\", label=f\"BIOSCREEN, t={time_bio[i]}d\")\n", + "plt.title(\"Comparison of BIOSCREEN and Anatrans models without decay\")\n", + "plt.legend()\n", + "plt.show()\n", + "\n", + "for i in range(len(time_bio)):\n", + " mbt.centerline(bio_ana_dc_results, time=time_bio[i], color=colors[i], label=f\"Anatrans, t={time_bio[i]}\")\n", + " plt.plot(bio_x, BIOSCREEN_data_lineardecay[i, :], color=colors_bio[i],\n", + " linestyle=\":\", label=f\"BIOSCREEN, t={time_bio[i]}d\")\n", + "plt.title(\"Comparison of BIOSCREEN and Anatrans models with linear decay\")\n", + "plt.legend()\n", + "plt.show()\n", + "\n", + "for i in range(len(time_bio)):\n", + " mbt.centerline(bio_ana_inst_results, time=time_bio[i], color=colors[i], label=f\"Anatrans, t={time_bio[i]}\")\n", + " plt.plot(bio_x, BIOSCREEN_data_instant[i, :], color=colors_bio[i],\n", + " linestyle=\":\", label=f\"BIOSCREEN, t={time_bio[i]}d\")\n", + "plt.title(\"Comparison of BIOSCREEN and Anatrans models with instant reaction\")\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "765613bdd5ab7be8", + "metadata": {}, + "source": [ + "### Input parameters\n", + "Input parameters are the default parameters when opening up BIOSCREEN-AT. All imperial units are converted to consistent metric units.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d10e013699b8f5d", + "metadata": {}, + "outputs": [], + "source": [ + "bat_hydro = mbt.HydrologicalParameters(\n", + " velocity=335.2 / ft / 365, #[m/d]\n", + " porosity=0.25, #[-]\n", + " alpha_x=28.887 / ft, #[m]\n", + " alpha_y=2.889 / ft, #[m]\n", + " alpha_z=0.289 / ft, #[m]\n", + ")\n", + "\n", + "bat_att = mbt.AttenuationParameters(\n", + " retardation=1.20672, #[-]\n", + " half_life=0, #[d]\n", + ")\n", + "\n", + "bat_source = mbt.SourceParameters(\n", + " source_zone_boundary=np.array([50 / ft]), #[m]\n", + " source_zone_concentration=np.array([9]), #[g/m3]\n", + " depth=10 / ft, #[m]\n", + " total_mass=\"inf\", #[g]\n", + ")\n", + "\n", + "bat_model = mbt.ModelParameters(\n", + " model_length=2500 / ft, #[m]\n", + " model_width=500 / ft, #[m]\n", + " model_time=5 * 365, #[d]\n", + " dx=20 / ft, #[m]\n", + " dy=5 / ft, #[m]\n", + " dt=365, #[d]\n", + ")\n", + "\n", + "bat_ea = mbt.ElectronAcceptors(\n", + " delta_oxygen=5.78, #[g/m3]\n", + " delta_nitrate=17, #[g/m3]\n", + " ferrous_iron=11.3, #[g/m3]\n", + " delta_sulfate=100, #[g/m3]\n", + " methane=0.414, #[g/m3]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "6fd5739844c6c6a4", + "metadata": {}, + "source": [ + "## Running model\n", + "As the exact solution uses an integral, run time is longer than that of the Bioscreen solution, depending on model resolution. Using verbose when running the model is recommended, to keep track of the calculation process." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7e1098849ca30e36", + "metadata": {}, + "outputs": [], + "source": [ + "# bat_mbt; BIOSCREEN-AT data with Mibitrans model\n", + "bat_mbt_object = mbt.Mibitrans(\n", + " hydrological_parameters=bat_hydro,\n", + " attenuation_parameters=bat_att,\n", + " source_parameters=bat_source,\n", + " model_parameters=bat_model,\n", + ")\n", + "bat_mbt_nd_results = bat_mbt_object.run()\n", + "\n", + "bat_mbt_object.attenuation_parameters.half_life = 365 * 10 #[d]\n", + "bat_mbt_dc_results = bat_mbt_object.run()\n", + "\n", + "bat_mbt_object.instant_reaction(electron_acceptors=bat_ea)\n", + "bat_mbt_inst_results = bat_mbt_object.run()" + ] + }, + { + "cell_type": "markdown", + "id": "aa774fd6b9df0d69", + "metadata": {}, + "source": [ + "## Visualize" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d0bb52e20f9ee6e0", + "metadata": {}, + "outputs": [], + "source": [ + "bat_mbt_nd_results.centerline(color=\"red\", label=\"No decay\")\n", + "bat_mbt_dc_results.centerline(color=\"blue\", label=\"Linear decay\")\n", + "bat_mbt_inst_results.centerline(color=\"green\", label=\"Instant reaction\")\n", + "\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "1beab531604f46c3", + "metadata": {}, + "source": [ + "Running Bioscreen solution for comparison" + ] + }, + { + "cell_type": "markdown", + "id": "ff9ec557bdfad139", + "metadata": {}, + "source": [ + "## Comparing to BIOSCREEN-AT\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7c122c684e746968", + "metadata": {}, + "outputs": [], + "source": [ + "bat_x = [0, 250 / ft, 500 / ft, 750 / ft, 1000 / ft, 1250 / ft, 1500 / ft, 1750 / ft, 2000 / ft, 2250 / ft, 2500 / ft]\n", + "BIOSCREENAT_data_nodecay = np.array([\n", + " [9.000, 3.635, 0.236, 0.001, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000],\n", + " [9.000, 4.649, 2.144, 0.452, 0.022, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000],\n", + " [9.000, 4.696, 2.707, 1.505, 0.493, 0.064, 0.003, 0.000, 0.000, 0.000, 0.000],\n", + " [9.000, 4.699, 2.767, 1.872, 1.161, 0.483, 0.104, 0.010, 0.000, 0.000, 0.000],\n", + " [9.000, 4.699, 2.772, 1.932, 1.423, 0.947, 0.459, 0.134, 0.021, 0.002, 0.000],\n", + "])\n", + "\n", + "BIOSCREENAT_data_lineardecay = np.array([\n", + " [9.000, 3.479, 0.222, 0.001, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000],\n", + " [9.000, 4.407, 1.944, 0.401, 0.019, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000],\n", + " [9.000, 4.448, 2.423, 1.289, 0.412, 0.053, 0.002, 0.000, 0.000, 0.000, 0.000],\n", + " [9.000, 4.450, 2.471, 1.580, 0.938, 0.380, 0.081, 0.008, 0.000, 0.000, 0.000],\n", + " [9.000, 4.450, 2.475, 1.624, 1.131, 0.721, 0.340, 0.097, 0.015, 0.001, 0.000],\n", + "])\n", + "\n", + "colors = [\"tomato\", \"sandybrown\", \"khaki\", \"greenyellow\", \"lightgreen\"]\n", + "colors_bio = [\"red\", \"orange\", \"gold\", \"yellowgreen\", \"green\"]\n", + "for i in range(len(bat_mbt_nd_results.t)):\n", + " mbt.centerline(bat_mbt_nd_results, time=bat_mbt_object.t[i],\n", + " color=colors[i], label=f\"Mibitrans, t={bat_mbt_object.t[i]}\")\n", + " plt.plot(bat_x, BIOSCREENAT_data_nodecay[i, :], color=colors_bio[i],\n", + " linestyle=\":\", label=f\"bio_at, t={bat_mbt_object.t[i]}\")\n", + "plt.legend()\n", + "plt.show()\n", + "\n", + "\n", + "for i in range(len(bat_mbt_dc_results.t)):\n", + " mbt.centerline(bat_mbt_dc_results, time=bat_mbt_object.t[i],\n", + " color=colors[i], label=f\"Mibitrans, t={bat_mbt_object.t[i]}\")\n", + " plt.plot(bio_x, BIOSCREENAT_data_lineardecay[i, :], color=colors_bio[i],\n", + " linestyle=\":\", label=f\"bio_at, t={bat_mbt_object.t[i]}\")\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "e9a6b6c841cf9302", + "metadata": {}, + "source": [ + "mibitrans output corresponds very well with the BIOSCREEN-AT output, and has higher resolution. Note that despite BIOSCREEN-AT taking in instant reaction parameters, it does not have the option to calculate biodegradation this way, therefore, instant reaction model can not be compared." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/example_exact_equation.ipynb b/examples/example_exact_equation.ipynb deleted file mode 100644 index e795b52..0000000 --- a/examples/example_exact_equation.ipynb +++ /dev/null @@ -1,373 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "2231e3885ab4c960", - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "from mibitrans.data.parameter_information import ElectronAcceptors\n", - "from mibitrans.data.parameters import AttenuationParameters\n", - "from mibitrans.data.parameters import HydrologicalParameters\n", - "from mibitrans.data.parameters import ModelParameters\n", - "from mibitrans.data.parameters import SourceParameters\n", - "from mibitrans.transport.models import Bioscreen\n", - "from mibitrans.transport.models import Mibitrans\n", - "from mibitrans.visualize.plot_line import centerline\n", - "from mibitrans.visualize.plot_surface import plume_3d" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1c04100008cf2f67", - "metadata": {}, - "outputs": [], - "source": [ - "ft = 3.281 # factor to convert ft to m" - ] - }, - { - "cell_type": "markdown", - "id": "642abbcc74f16426", - "metadata": {}, - "source": [ - "This example compares the Domenico (1987) analytical solution as implemented in the original BIOSCREEN with the exact solution described in Karanovic et al. (2007), implemented in BIOSCREEN-AT. The solution from Karanovic is adapted as follows:" - ] - }, - { - "cell_type": "markdown", - "id": "8fa244fd5005f090", - "metadata": {}, - "source": [ - "\\begin{equation}\n", - "C(x,y,z,t) = \\sum_{i=1}^{n}\\left(C^*_{0,i}\\frac{x}{8\\sqrt{\\pi D^{'}_{x}}}\\exp(-\\gamma t)\n", - "\\cdot \\int_{0}^{t}\\left[\\frac{1}{\\tau^{\\frac{3}{2}}} \\exp\\left((\\gamma - \\lambda_{EFF})\\tau - \\frac{(x-v^{'}\\tau)^2}{4D^{'}_{x}\\tau}\\right)\n", - "\\cdot \\left\\{ERFC\\left(\\frac{y-Y_i}{2 \\sqrt{D^{'}_{y}\\tau}}\\right)-ERFC\\left(\\frac{y+Y_i}{2 \\sqrt{D^{'}_{y}\\tau}}\\right) \\right\\}\n", - "\\cdot \\left\\{ERFC\\left(\\frac{z-Z}{2 \\sqrt{D^{'}_{z}\\tau}}\\right)-ERFC\\left(\\frac{z+Z}{2 \\sqrt{D^{'}_{z}\\tau}}\\right) \\right\\}\\right] d\\tau \\right)\n", - "\\end{equation}" - ] - }, - { - "cell_type": "markdown", - "id": "8efc6ab254154e63", - "metadata": {}, - "source": [ - "Which allows for source superposition and therefore multiple source zones. For instant reaction, BC is added to the outer source zone and subtracted from the resulting concentrations. Where (C(x,y,z,t) < 0) = 0" - ] - }, - { - "cell_type": "markdown", - "id": "765613bdd5ab7be8", - "metadata": {}, - "source": [ - "## Input parameters\n", - "Input parameters are the default parameters when opening up BIOSCREEN-AT. All imperial units are converted to consistent metric units.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d10e013699b8f5d", - "metadata": {}, - "outputs": [], - "source": [ - "hydro = HydrologicalParameters(\n", - " # Groundwater flow velocity, in [m/day]\n", - " velocity=335.2 / ft / 365,\n", - " # Effective soil porosity [-]\n", - " porosity=0.25,\n", - " # Longitudinal dispersivity, in [m]\n", - " alpha_x=28.887 / ft,\n", - " # Transverse horizontal dispersivity, in [m]\n", - " alpha_y=2.889 / ft,\n", - " # Transverse vertical dispersivity, in [m]\n", - " alpha_z=0.289 / ft,\n", - ")\n", - "\n", - "# Parameters for retardation and degradation\n", - "att = AttenuationParameters(\n", - " # Contaminant retardation factor [-]\n", - " retardation=1.20672,\n", - " # Contaminant half life, in [days]\n", - " half_life=0,\n", - ")\n", - "\n", - "# Parameters for source dimensions and concentrations\n", - "source = SourceParameters(\n", - " # Extent of source in the positive y-direction (total source extent is 2*50=100\n", - " source_zone_boundary=np.array([50 / ft]),\n", - " # Concentration in source zone\n", - " source_zone_concentration=np.array([9]),\n", - " # Source extent in z-direction\n", - " depth=10 / ft,\n", - " # Undissolved mass of contaminant source\n", - " total_mass=\"inf\",\n", - ")\n", - "\n", - "model = ModelParameters(\n", - " # Model extent in the longitudinal (x) direction in [m].\n", - " model_length=2500 / ft,\n", - " # Model extent in the transverse horizontal (y) direction in [m].\n", - " model_width=500 / ft,\n", - " # Model duration in [days].\n", - " model_time=5 * 365,\n", - " # Model grid discretization step size in the longitudinal (x) direction, in [m].\n", - " dx=20 / ft,\n", - " # Model grid discretization step size in the transverse horizontal (y) direction, in [m].\n", - " dy=5 / ft,\n", - " # Model time discretization step size, in [days]\n", - " dt=365,\n", - ")\n", - "\n", - "# Make object for electron acceptor concentration to use for instant reaction model\n", - "ea=ElectronAcceptors(\n", - " # Difference between background oxygen and current oxygen concentration in groundwater, in [g/m^3]\n", - " delta_oxygen=5.78,\n", - " # Difference between background nitrate and current nitrate concentration in groundwater, in [g/m^3]\n", - " delta_nitrate=17,\n", - " # Current ferrous iron concentration in groundwater, in [g/m^3]\n", - " ferrous_iron=11.3,\n", - " # Difference between background sulfate and current sulfate concentration in groundwater, in [g/m^3]\n", - " delta_sulfate=100,\n", - " # Current methane concentration in groundwater, in [g/m^3]\n", - " methane=0.414,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "6fd5739844c6c6a4", - "metadata": {}, - "source": [ - "## Running model\n", - "As the exact solution uses an integral, run time is longer than that of the Bioscreen solution, depending on model resolution. Using verbose when running the model is recommended, to keep track of the calculation process." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7e1098849ca30e36", - "metadata": {}, - "outputs": [], - "source": [ - "att.half_life = 0\n", - "mbt_nodecay = Mibitrans(\n", - " hydrological_parameters=hydro,\n", - " attenuation_parameters=att,\n", - " source_parameters=source,\n", - " model_parameters=model,\n", - " verbose=True,\n", - ")\n", - "nodecay_results = mbt_nodecay.run()\n", - "\n", - "# Set half life to a non-0 value to simulate linear decay\n", - "att.half_life = 10 * 365\n", - "\n", - "mbt_lindecay = Mibitrans(\n", - " hydrological_parameters=hydro,\n", - " attenuation_parameters=att,\n", - " source_parameters=source,\n", - " model_parameters=model,\n", - " verbose=True,\n", - ")\n", - "lindecay_results = mbt_lindecay.run()\n", - "\n", - "mbt_instant = Mibitrans(\n", - " hydrological_parameters=hydro,\n", - " attenuation_parameters=att,\n", - " source_parameters=source,\n", - " model_parameters=model,\n", - " verbose=True,\n", - ")\n", - "mbt_instant.instant_reaction(electron_acceptors=ea)\n", - "instant_results = mbt_instant.run()" - ] - }, - { - "cell_type": "markdown", - "id": "aa774fd6b9df0d69", - "metadata": {}, - "source": [ - "## Visualize" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d0bb52e20f9ee6e0", - "metadata": {}, - "outputs": [], - "source": [ - "centerline(nodecay_results, color=\"red\", label=\"No decay\")\n", - "centerline(lindecay_results, color=\"blue\", label=\"Linear decay\")\n", - "centerline(instant_results, color=\"green\", label=\"Instant reaction\")\n", - "plt.legend()\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8313008c644e49ae", - "metadata": {}, - "outputs": [], - "source": [ - "plume_3d(nodecay_results, cmap=\"viridis\")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "id": "1beab531604f46c3", - "metadata": {}, - "source": [ - "Running Bioscreen solution for comparison" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "47d5a37b375a3af4", - "metadata": {}, - "outputs": [], - "source": [ - "att.half_life = 0\n", - "bio_nodecay = Bioscreen(\n", - " hydrological_parameters=hydro,\n", - " attenuation_parameters=att,\n", - " source_parameters=source,\n", - " model_parameters=model,\n", - " verbose=True,\n", - ")\n", - "bio_nodecay_results = bio_nodecay.run()\n", - "\n", - "# Set half life to a non-0 value to simulate linear decay\n", - "att.half_life = 10 * 365\n", - "\n", - "bio_lindecay = Bioscreen(\n", - " hydrological_parameters=hydro,\n", - " attenuation_parameters=att,\n", - " source_parameters=source,\n", - " model_parameters=model,\n", - " verbose=True,\n", - ")\n", - "bio_lindecay_results = bio_lindecay.run()\n", - "\n", - "bio_instant = Bioscreen(\n", - " hydrological_parameters=hydro,\n", - " attenuation_parameters=att,\n", - " source_parameters=source,\n", - " model_parameters=model,\n", - " verbose=True,\n", - ")\n", - "bio_instant.instant_reaction(electron_acceptors=ea)\n", - "bio_instant_results = bio_instant.run()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "895bdb769ea63009", - "metadata": {}, - "outputs": [], - "source": [ - "centerline(nodecay_results, color=\"red\", label=\"No decay Mibitrans\")\n", - "centerline(lindecay_results, color=\"blue\", label=\"Linear decay Mibitrans\")\n", - "centerline(instant_results, color=\"green\", label=\"Instant reaction Mibitrans\")\n", - "centerline(bio_nodecay_results, color=\"tomato\", linestyle=\":\", label=\"No decay Bioscreen\")\n", - "centerline(bio_lindecay_results, color=\"lightblue\", linestyle=\":\", label=\"Linear decay Bioscreen\")\n", - "centerline(bio_instant_results, color=\"lightgreen\", linestyle=\":\", label=\"Instant reaction Bioscreen\")\n", - "plt.title(\"Comparison between Mibitrans and Bioscreen, at t=1825 days.\")\n", - "plt.legend()\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "id": "ff9ec557bdfad139", - "metadata": {}, - "source": [ - "## Comparing to BIOSCREEN-AT\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7c122c684e746968", - "metadata": {}, - "outputs": [], - "source": [ - "bio_x = [0, 250 / ft, 500 / ft, 750 / ft, 1000 / ft, 1250 / ft, 1500 / ft, 1750 / ft, 2000 / ft, 2250 / ft, 2500 / ft]\n", - "bio_at_nodecay = np.array(\n", - " (\n", - " [\n", - " [9.000, 3.635, 0.236, 0.001, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000],\n", - " [9.000, 4.649, 2.144, 0.452, 0.022, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000],\n", - " [9.000, 4.696, 2.707, 1.505, 0.493, 0.064, 0.003, 0.000, 0.000, 0.000, 0.000],\n", - " [9.000, 4.699, 2.767, 1.872, 1.161, 0.483, 0.104, 0.010, 0.000, 0.000, 0.000],\n", - " [9.000, 4.699, 2.772, 1.932, 1.423, 0.947, 0.459, 0.134, 0.021, 0.002, 0.000],\n", - " ]\n", - " )\n", - ")\n", - "\n", - "bio_at_lindecay = np.array(\n", - " (\n", - " [\n", - " [9.000, 3.479, 0.222, 0.001, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000],\n", - " [9.000, 4.407, 1.944, 0.401, 0.019, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000],\n", - " [9.000, 4.448, 2.423, 1.289, 0.412, 0.053, 0.002, 0.000, 0.000, 0.000, 0.000],\n", - " [9.000, 4.450, 2.471, 1.580, 0.938, 0.380, 0.081, 0.008, 0.000, 0.000, 0.000],\n", - " [9.000, 4.450, 2.475, 1.624, 1.131, 0.721, 0.340, 0.097, 0.015, 0.001, 0.000],\n", - " ]\n", - " )\n", - ")\n", - "colors = [\"tomato\", \"sandybrown\", \"khaki\", \"greenyellow\", \"lightgreen\"]\n", - "colors_bio = [\"red\", \"orange\", \"gold\", \"yellowgreen\", \"green\"]\n", - "for i in range(len(nodecay_results.t)):\n", - " centerline(nodecay_results, time=mbt_nodecay.t[i], color=colors[i], label=f\"Mibitrans, t={mbt_nodecay.t[i]}\")\n", - " plt.plot(bio_x, bio_at_nodecay[i, :], color=colors_bio[i], linestyle=\":\", label=f\"bio_at, t={mbt_nodecay.t[i]}\")\n", - "plt.legend()\n", - "plt.show()\n", - "\n", - "\n", - "for i in range(len(mbt_lindecay.t)):\n", - " centerline(lindecay_results, time=mbt_lindecay.t[i], color=colors[i], label=f\"Mibitrans, t={mbt_lindecay.t[i]}\")\n", - " plt.plot(bio_x, bio_at_lindecay[i, :], color=colors_bio[i], linestyle=\":\", label=f\"bio_at, t={mbt_lindecay.t[i]}\")\n", - "plt.legend()\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "id": "e9a6b6c841cf9302", - "metadata": {}, - "source": [ - "mibitrans output corresponds very well with the BIOSCREEN-AT output, and has higher resolution. Note that despite BIOSCREEN-AT taking in instant reaction parameters, it does not have the option to calculate biodegradation this way, therefore, instant reaction model can not be compared." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 2 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython2" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/example_keesler.ipynb b/examples/example_keesler.ipynb index de99624..f01dbdd 100644 --- a/examples/example_keesler.ipynb +++ b/examples/example_keesler.ipynb @@ -312,7 +312,6 @@ "ana_results_instant.centerline(time=time_point, label=\"Anatrans\", color=\"red\", linestyle=\"--\")\n", "bio_results_instant.centerline(time=time_point, label=\"Bioscreen\", color=\"green\", linestyle=\":\")\n", "\n", - "\n", "field_data_x = np.array([0, 32/ft, 64/ft, 192/ft, 288/ft])\n", "field_data_c = np.array([12,5,1,0.5,0.001])\n", "plt.scatter(field_data_x, field_data_c, label=\"field data\", color=\"black\")\n", From 103105c5f47c0f11b68b44eb794439f742bd4f79 Mon Sep 17 00:00:00 2001 From: Bakker Date: Mon, 16 Feb 2026 11:25:49 +0100 Subject: [PATCH 19/19] Finished example notebook for additional term --- examples/additional_term.ipynb | 185 ------------- examples/example_BIOSCREEN_comparison.ipynb | 4 +- examples/example_additional_term.ipynb | 248 ++++++++++++++++++ ...ipynb => example_mibitrans_anatrans.ipynb} | 0 ...y.ipynb => example_source_depletion.ipynb} | 0 5 files changed, 250 insertions(+), 187 deletions(-) delete mode 100644 examples/additional_term.ipynb create mode 100644 examples/example_additional_term.ipynb rename examples/{example_mibitrans_anatrans_comparison.ipynb => example_mibitrans_anatrans.ipynb} (100%) rename examples/{example_source_decay.ipynb => example_source_depletion.ipynb} (100%) diff --git a/examples/additional_term.ipynb b/examples/additional_term.ipynb deleted file mode 100644 index ada0f4f..0000000 --- a/examples/additional_term.ipynb +++ /dev/null @@ -1,185 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "initial_id", - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "from scipy.special import erfc\n", - "from scipy.special import erfcx\n", - "import mibitrans as mbt" - ] - }, - { - "cell_type": "markdown", - "id": "9fe57bdbc2e44bc8", - "metadata": {}, - "source": [ - "## Effects of the untruncated Domenico solution\n", - "\n", - "#### Introduction\n", - "\n", - "The analytical equation used in BIOSCREEN uses Domenico (1987) analytical solution for multidimensional transport of a contaminant species, but with the addition of source depletation and source superposition (to allow for more source zones). However, this solution is a truncation of the full analytical equation, which contains an additional term with a product of a exp and erfc function. This additional term does not require a lot of additional computing time in the context of mibitrans, and is therefore included in the 'Domenico' solution of this package. See equations below.\n", - "\n", - "(equation_in_bioscreen; truncated)\n", - "\n", - "(equation_in_mibitrans; untruncated)\n", - "\n", - "This notebook gives an overview of the differences between the truncated and untruncated Domenico solution, and briefly discusses the implementation in mibitrans.\n", - "\n", - "Note that exact analytical models (like Wexler (1992), which is the basis for the Karanovic solution, as implemented in BIOSCREEN-AT) still give more accurate results than the Domenico solution, regardless of truncation (West et al., 2007).\n" - ] - }, - { - "cell_type": "markdown", - "id": "80a3493193019b8c", - "metadata": {}, - "source": [ - "#### Over- and underflow of the additional term\n", - "\n", - "In mibitrans, 64-bit float numpy arrays are used for the calculations and as a consequence, which can hold values between approximately $10^{-300}$ and $10^{+300}$. While these are generally beyond any reasonable order of magnitude, certain parameter combinations can cause an underflow (value < $10^{-300}$ and therefore set to 0), or overflow (value > $10^{+300}$ and therefore set to infinity) to occur. Especially at distances further from the source. However, this does necessarily mean that the additional term is negligibly small, as for $a \\to \\infty$, $\\exp(a) \\to \\infty$ and $\\text{erfc}(a) \\to 0$. To combat this, the erfcx function is used, where $\\text{erfcx}(a) = \\exp(a^2) \\cdot \\text{erfc}(a)$. This transforms the additional term as: $\\exp(b) \\cdot \\text{erfc}(a) = \\exp(b - a^2) \\cdot \\text{erfcx}(b)$. Under any reasonable field condition, this expression of the additional will not over- or underflow. And is therefore implemented as such in mibitrans." - ] - }, - { - "cell_type": "markdown", - "id": "f2fefae6a58bddd2", - "metadata": {}, - "source": [ - "#### Visualization of the additional term\n", - "\n", - "Consider the following parameters as example field conditions" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "75cc19132fea97b0", - "metadata": {}, - "outputs": [], - "source": [ - "hydro = mbt.HydrologicalParameters(\n", - " velocity=0.1,\n", - " porosity=0.3,\n", - " alpha_x=1,\n", - " alpha_y=0,\n", - " alpha_z=0,\n", - ")\n", - "\n", - "att = mbt.AttenuationParameters(\n", - " retardation=1,\n", - ")\n", - "\n", - "source = mbt.SourceParameters(\n", - " source_zone_boundary=np.array([5,10,15]),\n", - " source_zone_concentration=np.array([15,10,5]),\n", - " total_mass=\"inf\",\n", - " depth=10,\n", - ")\n", - "\n", - "model = mbt.ModelParameters(\n", - " model_length=200,\n", - " model_width=50,\n", - " model_time=5*365,\n", - " dx=1,\n", - " dy=0.1,\n", - " dt=5,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fdfcc37598d37513", - "metadata": {}, - "outputs": [], - "source": [ - "ndeg_obj = mbt.Anatrans(hydro,att, source, model)\n", - "ndeg_obj.run()\n", - "\n", - "ndeg_obj.centerline(time=73, color=\"green\", label=\"t = 73 days\")\n", - "ndeg_obj.centerline(time=365, color=\"blue\", label=\"t = 365 days\")\n", - "ndeg_obj.centerline(time=3*365, color=\"red\", label=\"t = 10 days\")\n", - "plt.title(\"Concentration distribution\")\n", - "plt.legend()\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f39ffca3b2282a80", - "metadata": {}, - "outputs": [], - "source": [ - "365 * 3" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3fe5ad8151366b2d", - "metadata": {}, - "outputs": [], - "source": [ - "xxx = ndeg_obj.xxx\n", - "ttt = ndeg_obj.ttt\n", - "rv = ndeg_obj.rv\n", - "alpha_x = hydro.alpha_x\n", - "velocity = hydro.velocity\n", - "\n", - "\n", - "erfc_inner = (xxx + rv * ttt) / (2 * np.sqrt(alpha_x * rv * ttt))\n", - "term = np.exp(xxx / alpha_x - erfc_inner**2) * erfcx(erfc_inner)\n", - "plt.pcolormesh(xxx[None, None, :], ttt[:, None, None], term[:, 0, :])\n", - "plt.colorbar()\n", - "plt.show()\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1426ad092dec75e3", - "metadata": {}, - "outputs": [], - "source": [ - "exp_term = np.exp(xxx * rv / (alpha_x * rv))\n", - "erfc_term = erfc(erfc_inner)\n", - "term = exp_term * erfc_term\n", - "plt.pcolormesh(xxx[None, None, :], ttt[:, None, None], term[:, 0, :])\n", - "plt.colorbar()\n", - "plt.show()\n", - "\n", - "plt.plot(xxx[0, 0, :], np.log10(exp_term[0, 0, :]))\n", - "plt.show()\n", - "\n", - "plt.pcolormesh(xxx[None, None, :], ttt[:, None, None], np.log10(erfc_term[:, 0, :]))\n", - "plt.colorbar()\n", - "plt.show()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 2 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython2" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/example_BIOSCREEN_comparison.ipynb b/examples/example_BIOSCREEN_comparison.ipynb index 6bf6e17..7080e7b 100644 --- a/examples/example_BIOSCREEN_comparison.ipynb +++ b/examples/example_BIOSCREEN_comparison.ipynb @@ -348,7 +348,7 @@ "metadata": {}, "outputs": [], "source": [ - "bat_x = [0, 250 / ft, 500 / ft, 750 / ft, 1000 / ft, 1250 / ft, 1500 / ft, 1750 / ft, 2000 / ft, 2250 / ft, 2500 / ft]\n", + "bat_x = np.array([0, 250, 500, 750, 1000, 1250, 1500, 1750, 2000, 2250, 2500]) / ft #[m]\n", "BIOSCREENAT_data_nodecay = np.array([\n", " [9.000, 3.635, 0.236, 0.001, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000],\n", " [9.000, 4.649, 2.144, 0.452, 0.022, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000],\n", @@ -379,7 +379,7 @@ "for i in range(len(bat_mbt_dc_results.t)):\n", " mbt.centerline(bat_mbt_dc_results, time=bat_mbt_object.t[i],\n", " color=colors[i], label=f\"Mibitrans, t={bat_mbt_object.t[i]}\")\n", - " plt.plot(bio_x, BIOSCREENAT_data_lineardecay[i, :], color=colors_bio[i],\n", + " plt.plot(bat_x, BIOSCREENAT_data_lineardecay[i, :], color=colors_bio[i],\n", " linestyle=\":\", label=f\"bio_at, t={bat_mbt_object.t[i]}\")\n", "plt.legend()\n", "plt.show()" diff --git a/examples/example_additional_term.ipynb b/examples/example_additional_term.ipynb new file mode 100644 index 0000000..e30a500 --- /dev/null +++ b/examples/example_additional_term.ipynb @@ -0,0 +1,248 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "initial_id", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "from scipy.special import erfc\n", + "from scipy.special import erfcx\n", + "import mibitrans as mbt" + ] + }, + { + "cell_type": "markdown", + "id": "9fe57bdbc2e44bc8", + "metadata": {}, + "source": [ + "## Effects of the untruncated Domenico solution\n", + "\n", + "#### Introduction\n", + "\n", + "The analytical equation used in BIOSCREEN uses Domenico (1987) analytical solution for multidimensional transport of a contaminant species, but with the addition of source depletion and source superposition (to allow for more source zones), see equation below;\n", + "\n", + "\\begin{align}\\tag{1}\n", + " C(x, y, z, t) &= \\sum_{i=1}^{n}\\left\\{ \\frac{C^*_{0,i}}{8} \\exp \\left[-k_s \\left(t-\\frac{xR}{v} \\right)\\right] \\right. \\\\\n", + " &\\quad \\cdot \\left( \\exp \\left[ \\frac{x\\left(1-P\\right)}{2\\alpha_x}\\right]\n", + " \\cdot\\operatorname{erfc} \\left[ \\frac{(x - Pvt)}{2\\sqrt{\\alpha_x vt }} \\right] \\right) \\\\\n", + " &\\quad \\cdot \\left( \\operatorname{erf} \\left[ \\frac{y + Y_i/2}{2\\sqrt{\\alpha_y x}} \\right] - \\operatorname{erf} \\left[ \\frac{y - Y_i/2}{2\\sqrt{\\alpha_y x)}} \\right] \\right) \\\\\n", + " &\\quad \\cdot \\left. \\left( \\operatorname{erf} \\left[ \\frac{Z}{2\\sqrt{\\alpha_z x)}} \\right] - \\operatorname{erf} \\left[ \\frac{-Z}{2\\sqrt{\\alpha_z x}} \\right] \\right) \\right\\} \\\\\n", + " & \\text{with} \\quad P = \\sqrt{1+4\\mu \\alpha_x/v}\n", + "\\end{align}\n", + "\n", + "However, this solution (implemented as the Bioscreen solution in this package) is a truncation of the full analytical equation, which contains an additional term comprised of a product of an exp and erfc. See equation 2 below;\n", + "\n", + "\\begin{align}\\tag{2}\n", + " C(x, y, z, t) &= \\sum_{i=1}^{n}\\left\\{ \\frac{C^*_{0,i}}{8} \\exp \\left(-k_s t\\right) \\right. \\\\\n", + " &\\quad \\cdot \\left( \\exp \\left[ \\frac{x\\left(1-P\\right)}{2\\alpha_x}\\right]\n", + " \\cdot \\operatorname{erfc} \\left[ \\frac{x - Pvt}{2\\sqrt{\\alpha_x vt }} \\right] \\right.\\\\\n", + " &\\quad \\: + \\left. \\exp \\left[ \\frac{x\\left(1+P\\right)}{2\\alpha_x}\\right]\n", + " \\cdot \\operatorname{erfc} \\left[ \\frac{x + Pvt}{2\\sqrt{\\alpha_x vt }} \\right] \\right) \\\\\n", + " &\\quad \\cdot \\left( \\operatorname{erf} \\left[ \\frac{y + Y_i/2}{2\\sqrt{\\alpha_y x}} \\right] - \\operatorname{erf} \\left[ \\frac{y - Y_i/2}{2\\sqrt{\\alpha_y x)}} \\right] \\right) \\\\\n", + " &\\quad \\cdot \\left. \\left( \\operatorname{erf} \\left[ \\frac{Z}{2\\sqrt{\\alpha_z x)}} \\right] - \\operatorname{erf} \\left[ \\frac{-Z}{2\\sqrt{\\alpha_z x}} \\right] \\right) \\right\\} \\\\\n", + " & \\text{with} \\quad P = \\sqrt{1+4\\left(\\mu - k_s \\right) \\alpha_x/v}\n", + "\\end{align}\n", + "\n", + "As in the current age, the added computing time required for this term is negligible, it is included in the Anatrans model of this package.\n", + "\n", + "This notebook gives an overview of the differences between the truncated and untruncated Domenico solution, and briefly discusses the implementation in the mibitrans package. For ease of explanation, consider a situation with no decay or source depletion, with $\\mu = 0$ and $k_s = 0$, thus $P = 1$.\n", + "\n", + "Note that exact analytical models (like Wexler (1992), which is the basis for the mibitrans solution, as implemented in BIOSCREEN-AT) still give more accurate results than the Anatrans or solution, regardless of truncation (West et al., 2007). Furthermore, note the change in the definition of the source depletion term and P in equation 2. This difference is explained and investigated in 'example_source_depletion.ipynb'.\n" + ] + }, + { + "cell_type": "markdown", + "id": "6101211c57b2581b", + "metadata": {}, + "source": [ + "#### Over- and underflow of the additional term\n", + "\n", + "In mibitrans, 64-bit float numpy arrays are used for the calculations, consequently, it only can hold values between approximately $10^{-300}$ and $10^{+300}$. While these are generally beyond any reasonable order of magnitude, certain parameter combinations may cause an underflow (value < $10^{-300}$ and therefore set to 0), or overflow (value > $10^{+300}$ and therefore set to infinity) to occur. Especially at distances further from the source. However, this does not necessarily mean that the additional term is negligibly small, as for $a \\to \\infty$, $\\exp(a) \\to \\infty$ and $\\text{erfc}(a) \\to 0$, and $\\infty \\cdot 0$ is indeterminate. To combat this, the erfcx function is used, where $\\text{erfcx}(a) = \\exp(a^2) \\cdot \\operatorname{erfc}(a)$. This transforms the additional term as: $\\exp(b) \\cdot \\text{erfc}(a) = \\frac{\\exp(b)}{\\exp(a^2)} \\cdot \\exp(a^2) \\cdot \\operatorname{erfc}(a) = \\exp(b - a^2) \\cdot \\text{erfcx}(a)$. Under any reasonable field conditions, this expression of the additional term will not cause over- or underflow. And is therefore implemented as such in the Anatrans solution." + ] + }, + { + "cell_type": "markdown", + "id": "ec21cdd2b1177f6c", + "metadata": {}, + "source": [ + "#### Visualization of the additional term\n", + "\n", + "Consider the following parameters as example field conditions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9f1a89059aacfc33", + "metadata": {}, + "outputs": [], + "source": [ + "hydro = mbt.HydrologicalParameters(\n", + " velocity=0.1,\n", + " porosity=0.3,\n", + " alpha_x=1,\n", + " alpha_y=0,\n", + " alpha_z=0,\n", + ")\n", + "\n", + "att = mbt.AttenuationParameters(\n", + " retardation=1,\n", + ")\n", + "\n", + "source = mbt.SourceParameters(\n", + " source_zone_boundary=np.array([5,10,15]),\n", + " source_zone_concentration=np.array([15,10,5]),\n", + " total_mass=\"inf\",\n", + " depth=10,\n", + ")\n", + "\n", + "model = mbt.ModelParameters(\n", + " model_length=175,\n", + " model_width=50,\n", + " model_time=5*365,\n", + " dx=1,\n", + " dy=5,\n", + " dt=1,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "66f012e6a013c314", + "metadata": {}, + "outputs": [], + "source": [ + "ana_obj = mbt.Anatrans(hydro,att, source, model)\n", + "ana_ndeg = ana_obj.run()\n", + "\n", + "ana_ndeg.centerline(time=73, color=\"green\", label=\"t = 73 days\")\n", + "ana_ndeg.centerline(time=365, color=\"blue\", label=\"t = 365 days\")\n", + "ana_ndeg.centerline(time=3*365, color=\"red\", label=\"t = 3*365 days\")\n", + "plt.title(\"Anatrans concentration distribution at various times\")\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "536c63a4ccab3670", + "metadata": {}, + "source": [ + "Without considering transverse dispersion, source depletion, decay or multiple source zones, equation 1 would look like:\n", + "\n", + "\\begin{equation}\\tag{3}\n", + " C(x, t) = \\frac{C_{0}}{2} \\operatorname{erfc} \\left( \\frac{x - vt}{2\\sqrt{\\alpha_x vt }}\\right)\n", + "\\end{equation}\n", + "\n", + "And equation 2 would be:\n", + "\n", + "\\begin{equation}\\tag{4}\n", + " C(x, t) = \\frac{C_{0}}{2} \\left[ \\operatorname{erfc} \\left( \\frac{x - vt}{2\\sqrt{\\alpha_x vt }}\\right) + \\exp \\left(\\frac{x}{2\\alpha_x}\\right) \\cdot \\operatorname{erfc} \\left( \\frac{x + vt}{2\\sqrt{\\alpha_x vt }} \\right) \\right]\n", + "\\end{equation}\n", + "\n", + "The boundary value for continuous input is $C(x=0, t) = C_0$. However, when evaluated at $x=0$ and $t=0$, the erfc term in equation 3 resolves to $1$ and thus $C(x=0, t=0) = \\frac{C_0}{2}$. Therefore, equation 3 and by extension equation 1 do not abide by the boundary condition. This is visualized below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f33e9d250fa9a10d", + "metadata": {}, + "outputs": [], + "source": [ + "xxx = ana_obj.xxx\n", + "ttt = ana_obj.ttt\n", + "rv = ana_obj.rv\n", + "alpha_x = hydro.alpha_x\n", + "velocity = hydro.velocity\n", + "\n", + "# Calculate additional term under no decay conditions\n", + "erfc_inner = (xxx + rv * ttt) / (2 * np.sqrt(alpha_x * rv * ttt))\n", + "add_term = np.exp(xxx / alpha_x - erfc_inner**2) * erfcx(erfc_inner)\n", + "# Calculate the advection term\n", + "advec_term = erfc((xxx - rv * ttt) / (2 * np.sqrt(alpha_x * rv * ttt)))\n", + "\n", + "# Time does not evaluate at t=0, so add manually for visualization\n", + "t = np.zeros(len(ttt[:,0,0])+1)\n", + "t[1:] = ttt[:,0,0]\n", + "adv_x0 = np.zeros(len(ttt[:,0,0])+1)\n", + "add_x0 = np.zeros(len(ttt[:,0,0])+1)\n", + "# For x=0 and t=0, terms are equal to 1\n", + "adv_x0[0] = 1\n", + "add_x0[0] = 1\n", + "adv_x0[1:] = advec_term[:,0,0]\n", + "add_x0[1:] = add_term[:,0,0]\n", + "\n", + "plt.plot(t, adv_x0, color=\"blue\", label=\"advective term\")\n", + "plt.plot(t, add_x0, color=\"green\", label=\"additional term\")\n", + "plt.plot(t, adv_x0 + add_x0, linestyle=\":\", color=\"black\", label=\"sum\")\n", + "plt.xlabel(\"time [days]\")\n", + "plt.ylabel(\"value at x=0 [-]\")\n", + "plt.legend()\n", + "plt.show()\n" + ] + }, + { + "cell_type": "markdown", + "id": "26406a331e87db53", + "metadata": {}, + "source": [ + "Thus, only at around t = 150 days is the advective term is equal to $2$, and the boundary condition at $x=0$ satisfied. When including the additional term, the boundary condition is satisfied at all times. However, the error introduced by the truncation propagates, as shown in the figures below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "faccdf43d9bc6b7a", + "metadata": {}, + "outputs": [], + "source": [ + "plt.pcolormesh(xxx[None, None, :], ttt[:, None, None], add_term[:, 0, :])\n", + "plt.xlabel(\"x-position [m]\")\n", + "plt.ylabel(\"time [days]\")\n", + "plt.colorbar(label = \"value additional term [-]\")\n", + "plt.show()\n", + "\n", + "x = xxx[0, 0, :]\n", + "max_additional_x_term = np.max(add_term[:,0,:], axis = 0)\n", + "plt.plot(x, max_additional_x_term, color=\"black\")\n", + "plt.xlabel(\"time [days]\")\n", + "plt.ylabel(\"Maximum value additional term [-]\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "d4c6f54558068a02", + "metadata": {}, + "source": [ + "The value of the additional term is highest at $x=0$ and $t=0$. While the additional term decreases rapidly for higher values of $x$ and $t$, it remains relevant along the advective front. The size of the error that the truncation introduces is dependent on the Peclet number; $Pe = \\frac{vx}{D_x}$. And thus for input parameters, dependent on the flow velocity and longitudinal dispersivity." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/example_mibitrans_anatrans_comparison.ipynb b/examples/example_mibitrans_anatrans.ipynb similarity index 100% rename from examples/example_mibitrans_anatrans_comparison.ipynb rename to examples/example_mibitrans_anatrans.ipynb diff --git a/examples/example_source_decay.ipynb b/examples/example_source_depletion.ipynb similarity index 100% rename from examples/example_source_decay.ipynb rename to examples/example_source_depletion.ipynb