diff --git a/doc/conf.py b/doc/conf.py index 34ce6185f7bbe8898b345b50896c49e082074c23..24960f8bbb3ee559c302b8e6d9e156984d99261d 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -12,8 +12,8 @@ # import os import sys -sys.path.insert(0, os.path.abspath('..')) +sys.path.insert(0, os.path.abspath('..')) # -- Project information ----------------------------------------------------- @@ -24,20 +24,32 @@ author = 'Lukas Körber, Attila Kákay' # The full version, including alpha/beta/rc tags release = '2022' - # -- General configuration --------------------------------------------------- + # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ + 'nbsphinx', + 'sphinx_copybutton', # for "copy to clipboard" buttons 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', 'numpydoc', 'sphinx.ext.mathjax', - 'nbsphinx', + 'sphinx.ext.autosummary', 'sphinx_gallery.load_style' ] +# nbsphinx_thumbnails = { +# 'examples/round_tube_dispersion_vortex': 'examples/tube_eq_state.png', +# 'examples/round_tube_absorption': 'examples/tube_eq_state.png', +# 'examples/rectangular_waveguide_DE': 'examples/tube_eq_state.png', +# 'examples/FMR_rect_waveguide': 'examples/tube_eq_state.png', +# 'examples/hysteresis_loop': 'examples/tube_eq_state.png', +# 'examples/round_tube_spatial_dep_anis': 'examples/tube_eq_state.png', +# } + # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -46,7 +58,6 @@ templates_path = ['_templates'] # This pattern also affects html_static_path and html_extra_path. exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for @@ -60,6 +71,39 @@ html_logo = 'logo.png' # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] - # -- Extension configuration ------------------------------------------------- -numpydoc_xref_param_type = False + +autodoc_default_options = { + "inherited-members": False, + "undoc-members": False +} + +numpydoc_xref_param_type = True +numpydoc_xref_ignore = {'type', 'optional', 'default', 'or', 'of', 'method'} +numpydoc_xref_aliases = { + # python + 'sequence': ':term:`python:sequence`', + 'iterable': ':term:`python:iterable`', + 'string': 'str', + # numpy + 'array': 'numpy.ndarray', + 'dtype': 'numpy.dtype', + 'ndarray': 'numpy.ndarray', + 'array-like': ':term:`numpy:array_like`', + 'array_like': ':term:`numpy:array_like`', + + # tetrax + 'AbstractSample': 'tetrax.core.sample.AbstractSample', + 'MeshVector': 'tetrax.core.mesh.MeshVector', + 'FlattenedMeshVector': 'tetrax.core.mesh.FlattenedMeshVector', + 'LocalMeshVector': 'tetrax.core.mesh.LocalMeshVector', + 'MeshScalar': 'tetrax.core.mesh.MeshScalar', + 'FlattenedLocalMeshVector': 'tetrax.core.mesh.FlattenedLocalMeshVector', +} + +intersphinx_mapping = { + 'python': ('http://docs.python.org/', None), + 'numpy': ('https://www.numpy.org/devdocs', None), + 'scipy': ('http://docs.scipy.org/doc/scipy/reference/', None), + 'pandas': ('http://pandas.pydata.org/pandas-docs/dev', None), +} diff --git a/doc/examples.rst b/doc/examples.rst index fcbe68bbc77fd0a615bc13bfd2257aec5a3e1082..559f4ffc835f2a33518a22d85b70ae6f29f2091e 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -4,4 +4,13 @@ Examples This page provides a couple of examples showcasing the different use cases of TetraX. The examples are available in the form of Jupyter notebooks. .. nbgallery:: - examples/test_notebook + :caption: Examples: + :name: python gallery + + examples/round_tube_dispersion_vortex + examples/round_tube_absorption + examples/rectangular_waveguide_DE + examples/FMR_rect_waveguide + examples/hysteresis_loop + examples/round_tube_spatial_dep_anis + diff --git a/doc/examples/CPW_nanotube.png b/doc/examples/CPW_nanotube.png new file mode 100644 index 0000000000000000000000000000000000000000..983fe90500854f539654ea41e6545d3c9be8a57c Binary files /dev/null and b/doc/examples/CPW_nanotube.png differ diff --git a/doc/examples/FMR_rect_waveguide.ipynb b/doc/examples/FMR_rect_waveguide.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..22f052596afc0852f8c6f015a950a3e59d76cf09 --- /dev/null +++ b/doc/examples/FMR_rect_waveguide.ipynb @@ -0,0 +1,1147 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e8211be2", + "metadata": {}, + "source": [ + "# FMR simulation\n", + "\n", + "This notebook shows how to simulate the VNA-type FMR spectra, frequency versus external field for a fixed field angle, of a permalloy rectangular waveguide with 500nmx20nm dimensions. The static external field will be applied along the hard axis.\n", + "\n", + "The sample creation and mesh definition is taken to be known from the previous examples." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "8a431e0b", + "metadata": {}, + "outputs": [], + "source": [ + "import tetrax as tx\n", + "import numpy as np\n", + "\n", + "%matplotlib notebook\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "b67bd42c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Setting geometry and calculating discretized differential operators on mesh.\n", + "Done.\n" + ] + } + ], + "source": [ + "sample = tx.create_sample(name=\"FMR_rect_WG_500nmx20nm\")\n", + "sample.Msat = 800e3\n", + "sample.Aex = 13e-12\n", + "# Geometry of the rectanguar element, width x thickness\n", + "width = 500 # in units defined by the sample.scale\n", + "thickness = 20 # in units defined by the sample.scale\n", + "lca = 5 # tetrahedron edge length along width\n", + "lcb = 5 # tetrahedron edge length along thickness\n", + "mesh = tx.geometries.rectangle_cross_section(width,thickness,lca,lcb)\n", + "sample.set_geom(mesh)" + ] + }, + { + "cell_type": "markdown", + "id": "ba5b2f87", + "metadata": {}, + "source": [ + "### Dispersion and absorption calculation in a field loop\n", + "\n", + "In this section we create and experimental setup and set a homogeneous antenna. The external field will be looped between a minimum and maximum value with a given field step. For each static field value the equilibrium magnetization state will be obtained with relaxation. Then the dispersion is calculated for k=0 (FMR or homogeneous modes) only for the first 20 modes. This is followed by the calculation of the absorption, again for k=0. The total absorption is collected into a list that will be converted into a 2D numpy array and finally plotted as a 2D map, x axis being the external static fields, y axis the frequencies.\n", + "\n", + "NOTE: Depending on the sample size, underlaying mesh size, number of modes and number of field steps requested, this simulation can take between several minutes to several hours. Be patient!\n", + "Here, we use ```verbose=False``` for the relaxation, dispersion and absorption calculation. We advise you to set to ```True```, or if no argument is set, will by default be true.\n", + "To ensure that the equilibrium magnetization is proper, we use a ```while loop``` and give 5 trials for the ```relax()``` function." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "472d044d", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Relaxation with SLSQP method method was not succesful.\n", + "\n", + "\n", + "Relaxation with SLSQP method method was not succesful.\n", + "\n", + "\n", + "Relaxation with SLSQP method method was not succesful.\n", + "\n" + ] + } + ], + "source": [ + "Bmin = 0.1 # min. static field value\n", + "Bmax = 0.7 # max. static field value\n", + "Bstep = -2.5e-3 # field step\n", + "exp = tx.create_experimental_setup(sample)\n", + "bstatic = tx.vectorfields.homogeneous(sample.xyz, 90, 0)\n", + "exp.antenna = tx.core.experimental_setup.HomogeneousAntenna(90,90)\n", + "\n", + "total_absorb = []\n", + "ext_fields = np.arange(Bmax,Bmin+Bstep,Bstep)\n", + "\n", + "\n", + "for Bstat in ext_fields:\n", + " sample.mag = tx.vectorfields.homogeneous(sample.xyz, 60.0, 0.0) # arguments (coordinate, theta, phi)\n", + " exp.Bext = Bstat * bstatic\n", + " nr_trial = 0\n", + " success = False\n", + " while (not(success) and (nr_trial < 5)):\n", + " success = exp.relax(tol=1e-13,continue_with_least_squares=True,verbose=False)\n", + " nr_trial += 1\n", + " dispersion = exp.eigenmodes(num_cpus=-1,num_modes=20,k=0,verbose=False)\n", + " absorb_df = exp.absorption(k=0,verbose=False)\n", + " total_absorb.append(absorb_df[\"Re(chi)\"])\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "3ef20dc1", + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.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", + "\n", + "mpl.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_dpi_ratio', { dpi_ratio: fig.ratio });\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", + "\n", + "mpl.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", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\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", + " );\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('style', 'box-sizing: content-box;');\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; position: absolute; left: 0; top: 0; 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", + " 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", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\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 (fig.ws.readyState == 1 && 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", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.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", + " this.rubberband_canvas.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\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", + " evt.data.type = 'image/png';\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", + " evt.data\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 '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function 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", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * this.ratio;\n", + " var y = canvas_pos.y * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.which === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.which;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.which !== 17) {\n", + " value += 'ctrl+';\n", + " }\n", + " if (event.altKey && event.which !== 18) {\n", + " value += 'alt+';\n", + " }\n", + " if (event.shiftKey && event.which !== 16) {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k';\n", + " value += event.which.toString();\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.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", + "\n", + "mpl.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\n", + "var _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\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var 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.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", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(msg['content']['data']);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.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(\"<div id='\" + id + \"'></div>\");\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", + "\n", + "mpl.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", + " '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.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", + " '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n", + "};\n", + "\n", + "mpl.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", + "\n", + "mpl.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 = '<i class=\"fa ' + image + ' fa-lg\"></i>';\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 = '<i class=\"fa fa-power-off icon-remove icon-large\"></i>';\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", + "\n", + "mpl.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", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.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", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\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", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.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.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "<IPython.core.display.Javascript object>" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "<img src=\"data:image/png;base64,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\" width=\"640\">" + ], + "text/plain": [ + "<IPython.core.display.HTML object>" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "<ipython-input-4-b65c280dc5c2>:5: MatplotlibDeprecationWarning: shading='flat' when X and Y have the same dimensions as C is deprecated since 3.3. Either specify the corners of the quadrilaterals with X and Y, or pass shading='auto', 'nearest' or 'gouraud', or set rcParams['pcolor.shading']. This will become an error two minor releases later.\n", + " plt.pcolormesh(bb, ff, tt_abs.T)\n" + ] + } + ], + "source": [ + "tt_abs = np.array(total_absorb)\n", + "bb,ff = np.meshgrid(ext_fields*1e3,absorb_df[\"RF frequency (GHz)\"])\n", + "plt.pcolormesh(bb, ff, tt_abs.T)\n", + "plt.xlabel(\"External field (mT)\")\n", + "plt.ylabel(\"Frequency (GHz)\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "12e338b2", + "metadata": {}, + "source": [ + "In case in the 2D map for couple of field values the spectra looks wrong, means that the equilibration was not successfull. You might need to rerun for these particular field values. We are currently working on an implementation of a better energy minimization approach." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/examples/current_loop_nanotube_antenna.png b/doc/examples/current_loop_nanotube_antenna.png new file mode 100644 index 0000000000000000000000000000000000000000..699d79e787adbc47e5504a37acebe36bab8cc816 Binary files /dev/null and b/doc/examples/current_loop_nanotube_antenna.png differ diff --git a/doc/examples/hysteresis_loop.ipynb b/doc/examples/hysteresis_loop.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..a379c7e5e05433633694262dd3c5a391c9fffdda --- /dev/null +++ b/doc/examples/hysteresis_loop.ipynb @@ -0,0 +1,1109 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "aedbe279", + "metadata": {}, + "source": [ + "# Hysteresis loop calculation\n", + "\n", + "In this example we calculate the hysteresis loop of a magnetic waveguide with a round wire cross section (R=50 nm radius) and permalloy material parameters. " + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "4fc273d2", + "metadata": {}, + "outputs": [], + "source": [ + "import tetrax as tx\n", + "import numpy as np # will be required for code parts meant to collect the average magnetization for each field step\n", + "\n", + "%matplotlib notebook\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "0f08acae", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Setting geometry and calculating discretized differential operators on mesh.\n", + "Done.\n" + ] + } + ], + "source": [ + "sample = tx.create_sample(name=\"Round_wire_R_50nm\")\n", + "sample.Msat = 800e3\n", + "sample.Aex = 13e-12\n", + "mesh = tx.geometries.round_wire_cross_section(R=50)\n", + "sample.set_geom(mesh)" + ] + }, + { + "cell_type": "markdown", + "id": "7b562ee2", + "metadata": {}, + "source": [ + "In the next step we define the min and max field values, the step size. In a for loop over the static external field values we set the external field to the current value, we relax the magnetization and calculate the volume averaged x, y and z components, that are collected to an ```aver_mag``` list. Thes list will be converted into a numpy array and plotted." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "3b15f975", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "Bmin = -1 # min. static field value in [T]\n", + "Bmax = 1 # max. static field value in [T]\n", + "Bstep = 5e-3 # field step in [T]\n", + "\n", + "exp = tx.create_experimental_setup(sample)\n", + "# we define a homogeneous vectorfield in the x direction (theta=90,phi=0)\n", + "bhom = tx.vectorfields.homogeneous(sample.xyz, 90, 0)\n", + "# external field values in a numpy array: ext_fields\n", + "ext_fields = np.arange(Bmin,Bmax+Bstep,Bstep)\n", + "sample.mag = tx.vectorfields.homogeneous(sample.xyz, 60.0, 0.0) # arguments (coordinate, theta, phi)\n", + "\n", + "aver_mag = []\n", + "\n", + "for Bstat in ext_fields:\n", + " exp.Bext = Bstat * bhom # setting a homogeneous static field\n", + " nr_trial = 0\n", + " success = False\n", + " while (not(success) and (nr_trial < 5)):\n", + " success = exp.relax(tol=1e-13,continue_with_least_squares=True,verbose=False) # relaxation\n", + " nr_trial += 1\n", + " aver_m = tx.sample_average(sample.mag, sample) # computing the average mx, my, mz\n", + " aver_mag.append(aver_m) # collecting for each field step" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "51c7cb96", + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.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", + "\n", + "mpl.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_dpi_ratio', { dpi_ratio: fig.ratio });\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", + "\n", + "mpl.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", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\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", + " );\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('style', 'box-sizing: content-box;');\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; position: absolute; left: 0; top: 0; 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", + " 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", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\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 (fig.ws.readyState == 1 && 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", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.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", + " this.rubberband_canvas.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\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", + " evt.data.type = 'image/png';\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", + " evt.data\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 '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function 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", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * this.ratio;\n", + " var y = canvas_pos.y * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.which === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.which;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.which !== 17) {\n", + " value += 'ctrl+';\n", + " }\n", + " if (event.altKey && event.which !== 18) {\n", + " value += 'alt+';\n", + " }\n", + " if (event.shiftKey && event.which !== 16) {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k';\n", + " value += event.which.toString();\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.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", + "\n", + "mpl.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\n", + "var _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\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var 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.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", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(msg['content']['data']);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.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(\"<div id='\" + id + \"'></div>\");\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", + "\n", + "mpl.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", + " '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.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", + " '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n", + "};\n", + "\n", + "mpl.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", + "\n", + "mpl.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 = '<i class=\"fa ' + image + ' fa-lg\"></i>';\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 = '<i class=\"fa fa-power-off icon-remove icon-large\"></i>';\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", + "\n", + "mpl.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", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.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", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\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", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.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.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "<IPython.core.display.Javascript object>" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "<img src=\"data:image/png;base64,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9+vXT9QTqF6/cePGybx580S7hv/9738ngRVlhECEBFwfSYQ6kDUEgiGwbQxgMPKRCgEIFCbg2QN47LHHytixY2X8+PGii0Jff/318uWXX8qrr74qPXr0KCyZGAhAwH8CDJL3nykSIydgPYD4tyOvChRIEAHPHkBl0qtXL/NJEB+KCoF4ELBPSLrI4lEfaOErAde/jQXoK1eEQSAbAc8eQCtk/fr18t1338nChQvTPva6398rV640Xc+6B7F+tBv6559/zprNueeea7yU6qm0nwMOOCDtnnXr1smf//xns7PJDjvsIMcdd5wpV1oiTiAQGwJbH5F4AGNTIyjiI4GtLzjOL7aPQhEFAQhkI+DZAJw/f750795dqlWrJrvttpvo1nD6adasmfnOlklZrp155pkye/ZsmTBhgvnosRqBJYUjjzxSlixZ4n606zo1DBw4UF588UUZM2aMTJs2TdasWSO9e/eWTZs2pSbjGALxIIAHMB71gBaBEMADGAhWhEIgKwHPXcDqVatYsaKZ8NGoUSPjWcsq2YeLOsZQDb+ZM2dKly5djMR//OMf0rVrVzMBZa+99io2lypVqkjDhg2LvL5q1Sqze8k///lPOeKII0yap59+Wpo0aSKTJ0+mm7tIakRGS8B6AKPVgtwhEAQB+36DgzsIusiEQNEEPBuA6nmbNWuWtGrVqmhJAcTOmDHDdPta40+z0K5c7QqePn26ZDMA3377bdl5552ldu3acsghh8jNN99szlWGlkPXLuzZs6eemtC4cWNp27atkatjHYsK2m2sHxtWr15tD/mGQLAErIuELrJgOSM9EgIFdAFHwp1Mk03Acxfw3nvvLcuXLw+V1tKlS12jLTVjNez0WnHhqKOOkmeeeUb+85//yB133CEffPCB/OEPf3CNN71Xt7OrU6dOmogGDRpklTtixAhjfNrxiOoxJEAgHALWA8gYqXB4k0uYBHi/CZM2eUFgCwHPBuBtt90mV111lahnbcWKFaLer9RPaYAOHTrUnZxhJ2lkfn/44YdGpMZnBn1bLCrepjvttNPkmGOOMR49Xb7m9ddfl//+97/y2muv2SRFfpckd/DgwaLdx/azaNGiIuUQCQHfCdg+MjyAvqNFYHwIFP61j49uaAKBfCPguQvYjpU7/PDD0xhYo6k0kycuueQSOf3009PkZJ7o5JJPP/1Ufvjh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\" width=\"640\">" + ], + "text/plain": [ + "<IPython.core.display.HTML object>" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "aver_mag = np.array(aver_mag)\n", + "plt.plot(ext_fields*1e3,aver_mag[:,0])\n", + "plt.plot(-ext_fields*1e3,-aver_mag[:,0]) # here we only mirror the first loop to get a hysteresis\n", + "plt.xlabel(\"External field (mT)\")\n", + "plt.ylabel(\"Magnetization (normalized to Ms)\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "d64b3579", + "metadata": {}, + "source": [ + "NOTE: As mentioned in the documentation, for waveguide samples, the equilibration is not totally stable yet. Please use with care and check the resulting equilibrium states. Therefore might happen that re-running the hysteresis loop without changing any para,meters might lead to different results." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/examples/mag_uanis_sp_dep.png b/doc/examples/mag_uanis_sp_dep.png new file mode 100644 index 0000000000000000000000000000000000000000..221e53b3c15872a9cfbb8682a27c69799d40f7ab Binary files /dev/null and b/doc/examples/mag_uanis_sp_dep.png differ diff --git a/doc/examples/rect_waveguide_DE.png b/doc/examples/rect_waveguide_DE.png new file mode 100644 index 0000000000000000000000000000000000000000..25f3b573741a57325e4241ca318dc24571132979 Binary files /dev/null and b/doc/examples/rect_waveguide_DE.png differ diff --git a/doc/examples/rectangular_waveguide_DE.ipynb b/doc/examples/rectangular_waveguide_DE.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..4725129f313111d6431e364041aff03285974101 --- /dev/null +++ b/doc/examples/rectangular_waveguide_DE.ipynb @@ -0,0 +1,1164 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e8211be2", + "metadata": {}, + "source": [ + "# Rectangular waveguide disp. in Damon-Eshbach geometry\n", + "\n", + "This notebook will calculate the dispersion relation of the first 20 modes of a rectangular waveguide with 500 nm x 20 nm cross section, permalloy material parameters. To do so, first we create a sample with mesh of a rectangular cross section. A homogeneous initial magnetization state will be set and equilibrated using a 600 mT static external field. Then we calculate the dispersion for the first 20 modes between [kmin,kmax]. Using the ```matplotlib``` library the dispersion will be plotted for the first 5 modes." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "2b86cf35", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import meshio\n", + "import pygmsh\n", + "\n", + "import tetrax as tx\n", + "\n", + "%matplotlib notebook\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "b67bd42c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Setting geometry and calculating discretized differential operators on mesh.\n", + "Done.\n" + ] + } + ], + "source": [ + "sample = tx.create_sample(name=\"Rectangular_waveguide_500nmx40nm\")\n", + "sample.Msat = 800e3\n", + "sample.Aex = 13e-12\n", + "# Geometry of the rectanguar element, width x thickness\n", + "width = 500 # in units defined by the sample.scale\n", + "thickness = 20 # in units defined by the sample.scale\n", + "lca = 2 # tetrahedron edge length along width\n", + "lcb = 2 # tetrahedron edge length along thickness\n", + "mesh = tx.geometries.rectangle_cross_section(width,thickness,lca,lcb)\n", + "sample.set_geom(mesh)" + ] + }, + { + "cell_type": "markdown", + "id": "02fe8c63", + "metadata": {}, + "source": [ + "The initial state is homogeneous, oriented along (theta=60.0,phi=0.0) in spherical coordinates. Angles are given in degrees." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "472d044d", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "4258c68a1d804be7bfca0f0176362f66", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Output()" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "sample.mag = tx.vectorfields.homogeneous(sample.xyz, 60.0, 0.0) # arguments (coordinate, theta, phi)\n", + "sample.show()" + ] + }, + { + "cell_type": "markdown", + "id": "f829b189", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "3620e1a5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Minimizing in using 'L-BFGS-B' (tolerance 1e-13) ...\n", + "Current energy length density: -4.561143483919366e-09 J/m mx = 1.00 my = -0.00 mz = -0.00\n", + "Success!\n", + "\n" + ] + } + ], + "source": [ + "exp = tx.create_experimental_setup(sample)\n", + "bstatic = tx.vectorfields.homogeneous(sample.xyz, 90, 0)\n", + "exp.Bext = 600e-3 * bstatic\n", + "exp.relax(tol=1e-13)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "3c3be8e6", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|██████████| 41/41 [03:39<00:00, 5.35s/it]\n" + ] + } + ], + "source": [ + "dispersion = exp.eigenmodes(num_cpus=-1,num_modes=20,kmin=0,kmax=40e6,Nk=41)" + ] + }, + { + "cell_type": "markdown", + "id": "03722fac", + "metadata": {}, + "source": [ + "Plotting the dispersion of the first # number of modes." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "beb5a2d1", + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.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", + "\n", + "mpl.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_dpi_ratio', { dpi_ratio: fig.ratio });\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", + "\n", + "mpl.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", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\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", + " );\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('style', 'box-sizing: content-box;');\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; position: absolute; left: 0; top: 0; 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", + " 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", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\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 (fig.ws.readyState == 1 && 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", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.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", + " this.rubberband_canvas.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\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", + " evt.data.type = 'image/png';\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", + " evt.data\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 '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function 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", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * this.ratio;\n", + " var y = canvas_pos.y * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.which === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.which;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.which !== 17) {\n", + " value += 'ctrl+';\n", + " }\n", + " if (event.altKey && event.which !== 18) {\n", + " value += 'alt+';\n", + " }\n", + " if (event.shiftKey && event.which !== 16) {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k';\n", + " value += event.which.toString();\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.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", + "\n", + "mpl.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\n", + "var _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\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var 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.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", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(msg['content']['data']);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.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(\"<div id='\" + id + \"'></div>\");\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", + "\n", + "mpl.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", + " '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.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", + " '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n", + "};\n", + "\n", + "mpl.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", + "\n", + "mpl.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 = '<i class=\"fa ' + image + ' fa-lg\"></i>';\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 = '<i class=\"fa fa-power-off icon-remove icon-large\"></i>';\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", + "\n", + "mpl.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", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.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", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\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", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.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.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "<IPython.core.display.Javascript object>" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "<img src=\"data:image/png;base64,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a3RfCgQgAAEIQAACqSJAJpAaDHedRhKXk4fW3EngBVsgAGvQZi6FAAQgAAEIpJ1AnX5/xwRuJCyA2azee++9ctO92zsB1Lt37+zTkr2vKdqwKdxk95reQQACEIAABCBQDwQikTZh6tSpdtBBB5V29+CDD/bWAO63336mrV+/fvbCCy+UHucNBCAAAQhAAAIQgED1CURCAP71r3+1U045pVwvtC5QGULmzZtnF198sd12223ljrMDAQhAAAIQgAAEIFA9ApEQgDNnznTJHspne+jkAgV3cTHkunbt6onD6dOnV6mHY8eO9ayKSivXxmWpOP74423OnDl573HOOed4nsZ//vOf857DAQhAAAIQgAAEIJAEApEQgJ+51GTtlSFiW7n33nutXbt2wa7LFLarS0nrctJWoSiX8AUXXGAzZsyw559/3jZv3mwjRoxw6W1dftus8pgLuPzqq6+6BBUdso6wCwEIQAACEIAABJJHIBJOILLSabpXFj+V733ve+VI61hVg0A/88wz5e4x3gUpliXw9ddft8GDB5cek/gcPXq0PfvsszZy5MjSet5AAAIQgAAEIACBpBKIhAVw4MCBNmHChLyM77nnHtM5NSmrlcLMFVkTg7J161Zvevmyyy5Ln5dxAIFXCEAAAhCAAARSRyASFsAxY8bYUUcd5RJQtDKJMVnqVJYtW2bXX3+93efy4j733HPVHhxlEtEzBg0aZH369Cm9j+7d0AVRvuiii0rrKnqz0cXl0xaU7FR1QT2vEIAABCAAAQhAIMoEIiEAhw4darfccotdcsklNm7cOG+6V6nfZLWTQJNjxrBhw6rNUVO8s2fPtmnTppXeQ1PBN910k73xxhue80fpgQreyLHk2muvreAMDkEAAhCAAAQgAIHoE4hUJpDFixfbI488Yh999JFHrmfPnvaDH/zAOnfuXG2SF154ocnJY8qUKdatW7fS+0hUyiq4nbJrbCvKNax9PW/BggVBdelrmAVQ50qoVnWNYulNeQMBCEAAAhCAQL0SIBOIywTrpkdL6pV6PT1M3ZL4mzhxok2ePNkkJjOLvIo///zzzCo7+uijvTWBp59+uu21117ljoXt8AEKo0IdBCAAAQhAINoE+P42K/oUsOL7HXLIIQV9UhTCRZa5QtLCKQTM/fffb48//rjJy/iLL77wntGyZUtr2rSpt95Qaw4zS6NGjbzwM4WIv8zreA8BCEAAAhCAAATiRKBs/rNIrT711FNt+PDh9vDDD9u6detCW/H+++/b1VdfbT169PDW7IWelFWpzCGamh0yZIgXY1BxBrU99NBDWWeyCwEIQAACEIAABNJFoOgWQIm7O+64w37961/bySefbHvuuacXkHmHHXawVatW2YcffugFb1ZsQAV0zvTirWioqjOzHbbur6JncAwCEIAABCAAAQjEkUCk1gDKI3fq1KneNO8333xjrVu3tv3339/kJZwZvy8qoFlDEJWRoB0QgAAEIACBwgnw/R2BNYCZw9W/f3/TRoEABCAAAQhAAAIQqDsCRV8DWHdd484QgAAEIAABCEAAAmEEEIBhVKiDAAQgAAEIQAACCSaAAEzw4NI1CEAAAhCAAAQgEEYAARhGhToIQAACEIAABCCQYAKREoDz589PMGq6BgEIQAACEIAABKJBIFICUIGeFfLlvvvusw0bNkSDEK2AAAQgAAEIQAACCSMQKQH49ttve3H/Lr30Ui8l2znnnGOvvfZawpDTHQhAAAIQgAAEIFBcApESgMryMW7cOPvss89s/PjxXv7eQYMGebl/Vb98+fLi0uLpEIAABCAAAQhAIAEEIiUAA54NGza0E044wcsPfP3119snn3xiv/jFL6xTp06m3MGff/55cCqvEIAABCAAAQhAAAJVJBBJAThr1iw7//zzrX379p5FUOJPIvDFF1/0rIPHHXdcFbvJ6RCAAAQgAAEIQAACAYGGwZsovGqaV1O/c+bMsWOOOcYmTJjgvW63na9Tu3XrZnfccYftvffeUWgubYAABCAAAQhAAAKxJBApAXjbbbfZGWecYaeffrrnBBJGdPfdd7e777477BB1EIAABCAAAQhAAAIFEGhQ4koB53FKCIE1a9ZYy5YtbfXq1daiRYuQM6iCAAQgAAEIQCBqBPj+NovUGkBN//7zn//M+Zyo7t57782ppwICEIAABCAAAQhAoOoEIiUA//CHP1jr1q1zetGmTRv7/e9/n1NPBQQgAAEIQAACEIBA1QlESgAuXLjQ5OiRXbp06WKLFi3KrmYfAhCAAAQgAAEIQKAaBCIlAGXpmz17dk43lCGkVatWOfVUQAACEIAABCAAAQhUnUCkBOBJJ51kF110kU2aNMm2bNnibYr9d/HFF5uOUSAAAQhAAAIQgAAEak4gUmFgfve735mmgY888khTNhCVrVu3etk/WANY88HmDhCAAAQgAAEIQEAEIhkGZu7cuaZp36ZNm1rfvn1NawCjWHAjj+Ko0CYIQAACEIBAxQT4/jaLlAUwGK4999zTtFEgAAEIQAACEIAABGqfQKQEoNb93XPPPfaf//zHli1b5k3/ZnZZ6wEpEIAABCAAAQhAAAI1IxApAShnDwnAkSNHWp8+faxBgwY16x1XQwACEIAABCAAAQjkEIiUAHzwwQft4YcftmOOOSanoVRAAAIQgAAEIAABCNQOgUiFgWncuLH16NGjdnrGXSAAAQhAAAIQgAAEQglESgBeeumldtNNN1lJSUloY6mEAAQgAAEIQAACEKg5gUhNAU+bNs0LAv30009b7969rVGjRuV6+Oijj5bbZwcCEIAABCAAAQhAoOoEIiUAd955ZzvhhBOq3guugAAEIAABCEAAAhAomECkBOD48eMLbjgnQgACEIAABCAAAQhUj0Ck1gCqC5s3b7YXXnjB7rjjDlu7dq3XqyVLlti6deuq10OuggAEIAABCEAAAhAoRyBSFkDlAf7Od75jixYtso0bN9rw4cOtefPmdsMNN9iGDRvs9ttvL9d4diAAAQhAAAIQgAAEqk4gUhZABYI+8MADbdWqVV4e4KA7Wheo7CAUCEAAAhCAAAQgAIGaE4iUBVBewC+//LIpHmBm6dKli3322WeZVbyHAAQgAAEIQAACEKgmgUhZALdu3WrKB5xdPv30U28qOLuefQhAAAIQgAAEIACBqhOIlADUmr8///nPpb1QLmA5f/zmN78hPVwpFd5AAAIQgAAEIACBmhFo4LJuRCbthrx9hw4dattvv7199NFH3npAvbZu3dqmTJlibdq0qVlva/nqNWvWWMuWLW316tXWokWLWr47t4MABCAAAQhAoC4I8P1tFqk1gB06dLC33nrLHnjgAXvjjTdMU8I/+9nP7OSTTy7nFFIXHwbuCQEIQAACEIAABNJCIFIWwLhB5xdE3EaM9kIAAhCAAATM+P6OmAVwwoQJFX4uTz311AqPcxACEIAABCAAAQhAoHICkbIA7rLLLuVa/O2339rXX3/thYXZcccdbeXKleWOF3uHXxDFHgGeDwEIQAACEKg6Ab6/zSLlBawA0JmbPIDnzJljgwYN8tYFVn2IuQICEIAABCAAAQhAIJtApARgduO037NnT/vDH/5gyhJSlTJ27Fg76KCDvPiB8h4+/vjjPTEZ3EPWxSuuuML69u1rzZo1MzmgaIpZnsgUCEAAAhCAAAQgkGQCkReAgq+wMFUVZi+99JJdcMEFNmPGDHv++edt8+bNNmLECFu/fr03nppalqfxr371K+/10Ucftblz59p///d/J3m86RsEIAABCEAAAhCwSK0BfOKJJ8oNiUIUfv7553brrbda586d7emnny53vCo7y5cv9+IIShgOHjw49NKZM2fagAEDbOHChbb77ruHnpNZyRqCTBq8hwAEIAABCMSDAN/fEfMC1jRtZlEmkN12282GDRtmf/rTnzIPVfm9gjWr7Lrrrnmv1Tl65s4775z3HA5AAAIQgAAEIACBuBOIVCBoBX6uiyJL4pgxYzxnkj59+oQ+YsOGleZ0HgAANEJJREFUDXbllVfaT37yk7xZPTZu3GjagqJfEBQIQAACEIAABCAQNwKxWANYU6ijR4+22bNn5/UklkPISSed5GUe+etf/5r3cXIsUeq3YNO0NAUCEIAABCAAAQjEjUCk1gDKSldoGTduXEGnXnjhhfbYY495uYS7deuWc43E349+9CObN2+evfjii9aqVaucc4KKMAugRCC5gANCvEIAAhCAAASiT4A1gBFbA/jmm296Hrny2N1rr728T5A8c+UF3L9//9JPlNbpVVY07SvxN3HiRJs8ebJVJP4++ugjmzRpUoXiT89r0qSJt1X2bI5DAAIQgAAEIACBKBOI1BrAY4891ovbd++991qQFUSBoU8//XQ7/PDD7dJLLy2YpULA3H///fb444979/ziiy+8azV927RpUy8szA9+8ANPcP7f//2fbdmyxYJz5CjSuHHjgp/FiRCAAAQgAAEIQCBOBCI1BdyxY0d77rnnrHfv3uUYvvvuu14Mv6rEAsxnJRw/fryNGjXKFixYEGoV1INlDRwyZEi5NoTtYEIOo0IdBCAAAQhAINoE+P6O2BSwBmTp0qU5AnDZsmW2du3aKn2aNAVcUenatatVdk5F13MMAhCAAAQgAAEIxJVApLyATzjhBG+695FHHrFPP/3U2/T+Zz/7mX3ve9+LK2PaDQEIQAACEIAABCJFIFJrAG+//Xb7xS9+YT/96U9N3rkqDRs29ATgjTfeGClwNAYCEIAABCAAAQjElUCk1gAGEJWv95NP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\" width=\"640\">" + ], + "text/plain": [ + "<IPython.core.display.HTML object>" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "k_ = dispersion[\"k (rad/m)\"]\n", + "plt.figure()\n", + "for i in range(6):\n", + " plt.plot(k_*1e-6, dispersion[f\"f{i} (GHz)\"].values, ls=\"--\", linewidth=3, color=\"red\", alpha=0.5)\n", + " \n", + "plt.xlabel(\"Wave vector (rad/µm)\")\n", + "plt.ylabel(\"Frequency (GHz)\")\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/examples/round_tube_absorption.ipynb b/doc/examples/round_tube_absorption.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..9b4b9b19b9b5384a0c3a50372a127cac4345da37 --- /dev/null +++ b/doc/examples/round_tube_absorption.ipynb @@ -0,0 +1,2260 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e115187a", + "metadata": {}, + "source": [ + "# Absorption of a nanotube with antennae\n", + "\n", + "This notebook will calculate the dispersion relation of a nanotube with 60 nm outer and 40 nm inner diameters, permalloy material parameters. The obtained dispersion(s) must be equivalent to that(those) calculated analytically as well as with micromagnetic simulations published in: ```Jorge A. Otálora, Ming Yan, Helmut Schultheiss, Riccardo Hertel, and Attila Kákay, Phys. Rev. Lett. 117, 227203 (2016).```" + ] + }, + { + "cell_type": "markdown", + "id": "a2941d85", + "metadata": {}, + "source": [ + "First, we repeat the steps from the example on the dispersion simulation of nanotubes in vortex state. Then we set an antenna and compute the absorption." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "a0ea9478", + "metadata": {}, + "outputs": [], + "source": [ + "import tetrax as tx\n", + "\n", + "%matplotlib notebook\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "4220be52", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Setting geometry and calculating discretized differential operators on mesh.\n", + "Done.\n" + ] + } + ], + "source": [ + "sample = tx.create_sample(name=\"Nanotube_20nm_30nm\")\n", + "sample.Msat = 800e3\n", + "sample.Aex = 13e-12\n", + "\n", + "mesh = tx.geometries.tube_cross_section(20,30,lc=3)\n", + "sample.set_geom(mesh)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "c14f7e63", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Minimizing in using 'L-BFGS-B' (tolerance 1e-13) ...\n", + "Current energy length density: -6.727272024867091e-11 J/m mx = 0.00 my = -0.01 mz = 0.000\n", + "Success!\n", + "\n" + ] + } + ], + "source": [ + "sample.mag = tx.vectorfields.helical(sample.xyz, 60, 1)\n", + "exp = tx.create_experimental_setup(sample)\n", + "Bphi = tx.vectorfields.helical(sample.xyz, 90, 1) * 0.08\n", + "exp.Bext = Bphi\n", + "exp.relax(tol=1e-13,continue_with_least_squares=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "d63a97de", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|██████████| 81/81 [00:39<00:00, 2.03it/s]\n" + ] + } + ], + "source": [ + "dispersion = exp.eigenmodes(num_cpus=-1,num_modes=10,kmin=-40e6,kmax=40e6,Nk=81)" + ] + }, + { + "cell_type": "markdown", + "id": "f4ed3a81", + "metadata": {}, + "source": [ + "### Absorption computation\n", + "\n", + "#### Current loop antenna\n", + "\n", + "Here we show how to compute the dispersion one would measure when exciting with a given antenna type. First let's choose a current loop antenna and as a second example a couplanar antenna (CPW). We will visualize the antenna relative to the sample cross section.\n", + "The first antenna is a current loop with a given ```radius``` and ```width```." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "7d10e610", + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "eab3bd19e8c0464dadde23fb105bda77", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Output()" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "radius = 40\n", + "width = 5\n", + "exp.antenna = tx.core.experimental_setup.CurrentLoopAntenna(width,radius)\n", + "exp.show()" + ] + }, + { + "cell_type": "markdown", + "id": "05350fc2", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "id": "e2fabecb", + "metadata": {}, + "source": [ + "Compute the dispersion one can excite with the current loop antenna:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "4956be1b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Calculating absorption.\n", + "Done.\n" + ] + } + ], + "source": [ + "absorb, wavevectors, frequencies = exp.absorption()" + ] + }, + { + "cell_type": "markdown", + "id": "059263f3", + "metadata": {}, + "source": [ + "Let's plot it:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "e59cbe1f", + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.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", + "\n", + "mpl.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_dpi_ratio', { dpi_ratio: fig.ratio });\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", + "\n", + "mpl.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", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\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", + " );\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('style', 'box-sizing: content-box;');\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; position: absolute; left: 0; top: 0; 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", + " 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", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\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 (fig.ws.readyState == 1 && 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", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.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", + " this.rubberband_canvas.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\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", + " evt.data.type = 'image/png';\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", + " evt.data\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 '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function 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", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * this.ratio;\n", + " var y = canvas_pos.y * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.which === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.which;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.which !== 17) {\n", + " value += 'ctrl+';\n", + " }\n", + " if (event.altKey && event.which !== 18) {\n", + " value += 'alt+';\n", + " }\n", + " if (event.shiftKey && event.which !== 16) {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k';\n", + " value += event.which.toString();\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.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", + "\n", + "mpl.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\n", + "var _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\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var 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.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", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(msg['content']['data']);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.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(\"<div id='\" + id + \"'></div>\");\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", + "\n", + "mpl.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", + " '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.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", + " '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n", + "};\n", + "\n", + "mpl.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", + "\n", + "mpl.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 = '<i class=\"fa ' + image + ' fa-lg\"></i>';\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 = '<i class=\"fa fa-power-off icon-remove icon-large\"></i>';\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", + "\n", + "mpl.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", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.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", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\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", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.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.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "<IPython.core.display.Javascript object>" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "<img src=\"data:image/png;base64,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LrY0dUYAAQQQQAABBL4WIADkUkAAAQQQQAABBBwTIAB0rMGpLgIIIIAAAgggQADINYAAAggggAACCDgmQADoWINTXQQQQAABBBBAgACQawABBBBAAAEEEHBMgADQsQanuggggAACCCCAAAEg1wACCCCAAAIIIOCYAAGgYw1OdRFAAAEEEEAAAQJArgEEEEAAAQQQQMAxAQJAxxqc6iKAAAIIIIAAAgSAXAMIIIAAAggggIBjAgSAjjU41UUAAQQQQAABBAgAuQYQQAABBBBAAAHHBAgAHWtwqosAAggggAACCBAAcg0ggAACCCCAAAKOCRAAOtbgVBcBBBBAAAEEECAA5BpAAAEEEEAAAQQcEyAAdKzBqS4CCCCAAAIIIEAAyDWAAAIIIIAAAgg4JkAA6FiDU10EEEAAAQQQQIAAkGsAAQQQQAABBBBwTIAA0LEGp7oIIIAAAggggAABINcAAggggAACCCDgmAABoGMNTnURQAABBBBAAAECQK4BBBBAAAEEEEDAMQECQMcanOoigAACCCCAAAIEgFwDCCCAAAIIIICAYwIEgI41ONVFAAEEEEAAAQQIALkGEEAAAQQQQAABxwQIAB1rcKqLAAIIIIAAAggQAHINIIAAAggggAACjgkQADrW4FQXAQQQQAABBBAgAOQaQAABBBBAAAEEHBMgAHSswakuAggggAACCCBAAMg1gAACCCCAAAIIOCZAAOhYg1NdBBBAAAEEEECAAJBrAAEEEEAAAQQQcEyAANCxBqe6CCCAAAIIIIAAASDXAAIIIIAAAggg4JgAAaBjDU51EUAAAQQQQAABAkCuAQQQQAABBBBAwDEBAkDHGpzqIoAAAggggAACBIBcAwgggAACCCCAgGMCBICONTjVRQABBBBAAAEECAC5BhBAAAEEEEAAAccECAAda3CqiwACCCCAAAIIEAByDSCAAAIIIIAAAo4JEAA61uBUFwEEEEAAAQQQIADkGkAAAQQQQAABBBwTIAB0rMGpLgIIIIAAAgggENsAcOLEidKvXz8pKiqSjh07yqhRo2Tx4sUZLT569GhJJBIZr+OOOy5jGz4ggAACCCCAAAJxE4htADh37ly54oorZP78+TJ79myprKyUYcOGSVlZWUYbnnLKKbJ69erUa+bMmRnr+YAAAggggAACCMRNoDBuFQrr89xzz4Vvg59Tp04N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GevtY836hbe2Ncun5+oPdAkycZdffnkQeGmwWt1F+zyqRdiPMdf3NcjVTKneOtesrGYxNWBOX+64447AUOupQfWAAQOCkcPhNlo3Hbkb1i8s9weBBBlMnepF5yScPHlyMDm1Bpl7s2ifTJ3e58wzz9yb3fBdBBBoAIGEf8vgq84tDXBwDokAAgjEXUBvi2uwptk3vY1cl4ve8tZANRx8o8fS4F37Em7evLnWD63T7ujtX3/kdK3vmx0igEDdCpABrFtf9o4AAo4L6GhcnUxZs511uehgkTPOOEOGDx9el4dJ7VufBax9FX/+85+nyniDAAL5I0AGMH/aijNFAAEEqi1QlxnAap8MX0AAgcgIEABGpik4EQQQQAABBBBAoH4EuAVcP84cBQEEEEAAAQQQiIwAAWBkmoITQQABBBBAAAEE6keAALB+nDkKAggggAACCCAQGQECwMg0BSeCAAIIIIAAAgjUjwABYP04cxQEEEAAAQQQQCAyAgSAkWkKTgQBBBBAAAEEEKgfAQLA+nHmKAgggAACCCCAQGQECAAj0xScCAIIIIAAAgggUD8CBID148xREEAAAQQQQACByAj8f03HgpRFAOL6AAAAAElFTkSuQmCC\" width=\"640\">" + ], + "text/plain": [ + "<IPython.core.display.HTML object>" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "<ipython-input-7-eb5f2f004883>:2: MatplotlibDeprecationWarning: shading='flat' when X and Y have the same dimensions as C is deprecated since 3.3. Either specify the corners of the quadrilaterals with X and Y, or pass shading='auto', 'nearest' or 'gouraud', or set rcParams['pcolor.shading']. This will become an error two minor releases later.\n", + " plt.pcolormesh(wavevectors*1e-6, frequencies, absorb)\n" + ] + } + ], + "source": [ + "plt.figure()\n", + "plt.pcolormesh(wavevectors*1e-6, frequencies, absorb)\n", + "plt.xlabel(\"Wave vector (rad/µm)\")\n", + "plt.ylabel(\"Frequency (GHz)\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "4a837318", + "metadata": {}, + "source": [ + "### Couplanar-wave antenna\n", + "\n", + "#### NOTE: no need to recompute the dispersion itself, only the absorption is recalculated based on the new antenna geometry.\n", + "\n", + "The second antenna is a CPW:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "c3591789", + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "eab24cc242b44f8984063ec48f17a74b", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Output()" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "exp.antenna = tx.core.experimental_setup.CPWAntenna(20,58,35)\n", + "exp.show()" + ] + }, + { + "cell_type": "markdown", + "id": "f98bf2d9", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "id": "6395f03b", + "metadata": {}, + "source": [ + "Compute and plot the absorption:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "95cd3289", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Calculating absorption.\n", + "Done.\n" + ] + }, + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.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", + "\n", + "mpl.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_dpi_ratio', { dpi_ratio: fig.ratio });\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", + "\n", + "mpl.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", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\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", + " );\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('style', 'box-sizing: content-box;');\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; position: absolute; left: 0; top: 0; 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", + " 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", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\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 (fig.ws.readyState == 1 && 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", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.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", + " this.rubberband_canvas.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\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", + " evt.data.type = 'image/png';\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", + " evt.data\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 '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function 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", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * this.ratio;\n", + " var y = canvas_pos.y * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.which === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.which;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.which !== 17) {\n", + " value += 'ctrl+';\n", + " }\n", + " if (event.altKey && event.which !== 18) {\n", + " value += 'alt+';\n", + " }\n", + " if (event.shiftKey && event.which !== 16) {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k';\n", + " value += event.which.toString();\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.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", + "\n", + "mpl.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\n", + "var _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\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var 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.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", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(msg['content']['data']);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.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(\"<div id='\" + id + \"'></div>\");\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", + "\n", + "mpl.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", + " '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.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", + " '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n", + "};\n", + "\n", + "mpl.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", + "\n", + "mpl.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 = '<i class=\"fa ' + image + ' fa-lg\"></i>';\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 = '<i class=\"fa fa-power-off icon-remove icon-large\"></i>';\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", + "\n", + "mpl.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", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.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", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\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", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.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.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "<IPython.core.display.Javascript object>" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "<img src=\"data:image/png;base64,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QKCVBBqDjfKcPCLal1/vGJbEJZljn5PY0tQZAQQQQAABBBD4WIAAkI8CAggggAACCCCQMAECwIQ1ONVFAAEEEEAAAQQIAPkMIIAAAggggAACCRMgAExYg1NdBBBAAAEEEECAAJDPAAIIIIAAAgggkDABAsCENTjVRQABBBBAAAEECAD5DCCAAAIIIIAAAgkTIABMWINTXQQQQAABBBBAgACQzwACCCCAAAIIIJAwAQLAhDU41UUAAQQQQAABBAgA+QwggAACCCCAAAIJEyAATFiDU10EEEAAAQQQQIAAkM8AAggggAACCCCQMAECwIQ1ONVFAAEEEEAAAQQIAPkMIIAAAggggAACCRMgAExYg1NdBBBAAAEEEECAAJDPAAIIIIAAAgggkDABAsCENTjVRQABBBBAAAEECAD5DCCAAAIIIIAAAgkTIABMWINTXQQQQAABBBBAgACQzwACCCCAAAIIIJAwAQLAhDU41UUAAQQQQAABBAgA+QwggAACCCCAAAIJEyAATFiDU10EEEAAAQQQQIAAkM8AAggggAACCCCQMAECwIQ1ONVFAAEEEEAAAQQIAPkMIIAAAggggAACCRMgAExYg1NdBBBAAAEEEECAAJDPAAIIIIAAAgggkDABAsCENTjVRQABBBBAAAEECAD5DCCAAAIIIIAAAgkTIABMWINTXQQQQAABBBBAgACQzwACCCCAAAIIIJAwAQLAhDU41UUAAQQQQAABBAgA+QwggAACCCCAAAIJEyAATFiDU10EEEAAAQQQQIAAkM8AAggggAACCCCQMAECwIQ1ONVFAAEEEEAAAQQIAPkMIIAAAggggAACCRMgAExYg1NdBBBAAAEEEECAAJDPAAIIIIAAAgggkDABAsCENTjVRQABBBBAAAEECAD5DCCAAAIIIIAAAgkTIABMWINTXQQQQAABBBBAgACQzwACCCCAAAIIIJAwAQLAhDU41UUAAQQQQAABBAgA+QwggAACCCCAAAIJEyAATFiDU10EEEAAAQQQQIAAkM8AAggggAACCCCQMAECwIQ1ONVFAAEEEEAAAQQqNgCcOHGi7L///tKjRw/p37+/jB8/Xt58882cFj/ttNMklUrl/Hz2s5/NWYcnCCCAAAIIIIBApQlUbAA4bdo0Ofvss2X69OkydepUaWxslLFjx0pDQ0NOGx555JGycOHCzM/j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JhDeyk804Js5c2bm0DTQGzhwoMyYMUPCe6JmysP7pcrgwYNl5MiRmTIe2AKa5fOW8P7Z8q1vfct7OSrfuHFjwdeLfXH58uXy0ksvydFHH13sW4ta/6STTopOFP71r38V9T5WRgCBthcgAGz7NuAIECibwM477xwFdRrcpRd9fMwxx8iOO+4YBQ3Z5Row6nL33XfLfvvtF2UPNVg88cQTZcmSJdFr4b00ZciQIc2yTa+88kqUQXznnXei9erq6uQ//uM/pH///tKzZ08ZM2aM/P3vf49es/458MAD5fvf/37OSx9++GGUrQzvYxuVa+B1ySWXyLbbbivdunWTAw44QLLrpivppdfw/p/StWtX2WabbeQLX/iCrFixQk477TQJ7/ErP/vZz6Lj1Gznu+++G21Xyz/zmc+IZrkGDRoUHUd21lSza+ecc45MmDBB+vbtK0cccUT0vvx/1OCtt96S8J7gmZd0H7qvBx54QHQ7nTt3jnyXLVsmX//61yNLPdY999xT7r333sz79EFDQ4Occsop0r179+i4fvrTn+a8nn7y2GOPyd577x253HnnndKrV6/0S9HvP/zhD9ExpAuvvPJK+dSnPiW/+c1vosu6uv3vfve7smnTJrn++uujEwRttx//+Mfpt0S/+/TpIwcddFCz48xZiScIIBBLAQLAWDYLB4VA6wlo0JEOoHQv+ljLNEhKl2tg9Ze//EXSAaA+/9GPfhQFbBo8zJ07Nwqg9P1VVVVywgknyG9/+1t9mlnuuece0SBuhx12EL3luAZBixYtkscffzzqN7bvvvvK5z//edFslbVodkkDoOzbld9///0yYMCA6Fj1Pd/85jejAO++++6TWbNmyVe+8hU58sgjZc6cOdEmX3vttWgfu+++e1SfF198McqKaWCjgZ8e35lnnikLFy6MfoYOHSoLFiyQo446Svbff/+ovpMnT5bbb79drr766pzDnDJlitTU1ET7v+2223JeSz95/vnnowyqBrz5i146/d73vieaPdOgdN26dfLpT39a/u///k/+8Y9/RMHyySefLH/9618zb7344oujNnr44Yfl6aefjoJdqw/eo48+GgX1mTduwYO3335bnnjiCXnyyScjdw0Gtc3ef//9KFC+7rrr5D//8z9l+vTpOVvTQFmzyCwIINDOBMIvVxYEEEiQQNgnLQizZUF42TGo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\" width=\"640\">" + ], + "text/plain": [ + "<IPython.core.display.HTML object>" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "<ipython-input-9-5935c9a43130>:3: MatplotlibDeprecationWarning: shading='flat' when X and Y have the same dimensions as C is deprecated since 3.3. Either specify the corners of the quadrilaterals with X and Y, or pass shading='auto', 'nearest' or 'gouraud', or set rcParams['pcolor.shading']. This will become an error two minor releases later.\n", + " plt.pcolormesh(wavevectors*1e-6, frequencies, absorb)\n" + ] + } + ], + "source": [ + "absorb, wavevectors, frequencies = exp.absorption()\n", + "plt.figure()\n", + "plt.pcolormesh(wavevectors*1e-6, frequencies, absorb)\n", + "plt.xlabel(\"Wave vector (rad/µm)\")\n", + "plt.ylabel(\"Frequency (GHz)\")\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/examples/round_tube_dispersion_vortex.ipynb b/doc/examples/round_tube_dispersion_vortex.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..cebd3275e268e964542637b2be1712ffd4e68ac1 --- /dev/null +++ b/doc/examples/round_tube_dispersion_vortex.ipynb @@ -0,0 +1,1354 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e115187a", + "metadata": {}, + "source": [ + "# Dispersion of a nanotube in vortex state\n", + "\n", + "This notebook will calculate the dispersion relation of a nanotube with 60 nm outer and 40 nm inner diameters, permalloy material parameters. The obtained dispersion(s) must be equivalent to that(those) calculated analytically as well as with micromagnetic simulations published in: ```Jorge A. Otálora, Ming Yan, Helmut Schultheiss, Riccardo Hertel, and Attila Kákay, Phys. Rev. Lett. 117, 227203 (2016).```" + ] + }, + { + "cell_type": "markdown", + "id": "a2941d85", + "metadata": {}, + "source": [ + "First we need to import the ```TetraX``` package.\n", + "The ```%matplotlib notebook``` and ```import matplotlib.pyplot as plt``` are for plotting the obtained dispersions." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "a0ea9478", + "metadata": {}, + "outputs": [], + "source": [ + "import tetrax as tx\n", + "%matplotlib notebook\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "id": "fe359475", + "metadata": {}, + "source": [ + "Here, we create a sample with permalloy like material parameters. By default the material parameters are set to those of permalloy. Each sample needs a geometry in the form of a mesh. We create a geometry with tubular cross section, 30nm outer and 20 nm inner radii. To do so, we use the ```tube_cross_section(r,R,lc,x0,y0)``` geometry function from the geometries module." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "4220be52", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Setting geometry and calculating discretized differential operators on mesh.\n", + "Done.\n" + ] + } + ], + "source": [ + "sample = tx.create_sample(name=\"Nanotube_20_30\")\n", + "sample.Msat = 800e3\n", + "sample.Aex = 13e-12\n", + "sample.Didmi = 0\n", + "mesh = tx.geometries.tube_cross_section(20,30,lc=3)\n", + "sample.set_geom(mesh)" + ] + }, + { + "cell_type": "markdown", + "id": "8e10b619", + "metadata": {}, + "source": [ + "The ```sample``` parameters, material and mesh, can be listed by typing ```sample```." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "37359cb3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "<p><h3>Sample: Nanotube_20_30</h3></p>\n", + " <p><table align=\"left\">\n", + " <tr>\n", + " <th>Material parameter</th>\n", + " <th>Attribute name</th>\n", + " <th>Value</th>\n", + " </tr>\n", + " <tr>\n", + " <td>Saturation magnetization </td><td><tt>Msat</tt></td>\n", + " <td>800000.0 A/m</td>\n", + " </tr>\n", + " <tr>\n", + " <td>Exchange stiffness constant </td><td><tt>Aex</tt></td>\n", + " <td>1.3e-11 J/m</td>\n", + " </tr>\n", + " <tr>\n", + " <td>Gyromagnetic ratio </td><td><tt>gamma</tt></td>\n", + " <td>176085964400.0 rad Hz/T</td>\n", + " </tr>\n", + " <tr>\n", + " <td>Gilbert damping parameter </td><td><tt>alpha</tt></td>\n", + " <td>0.01</td>\n", + " </tr>\n", + " <tr>\n", + " <td>Unaxial anistropy constant </td><td><tt>Ku1</tt></td>\n", + " <td>0.0 J/m<sup>3</sup></td>\n", + " </tr>\n", + " <tr>\n", + " <td>Unaxial anistropy angles </td><td><tt>k_phi</tt><br><tt>k_theta</tt></td>\n", + " <td>0.0°<br>0.0°</td>\n", + " </tr>\n", + " <tr>\n", + " <td>Cubic anistropy constant</td><td><tt>Kc1</tt></td>\n", + " <td>0.0 J/m<sup>3</sup></td>\n", + " </tr>\n", + " <tr>\n", + " <td>Cubic anistropy axes</td><td><tt>v1Kc</tt><br><tt>v2Kc</tt></td>\n", + " <td>(1, 0, 0)<br>(0, 1, 0)</td>\n", + " </tr>\n", + " <tr>\n", + " <td>Bulk DMI constant </td><td><tt>Dbulk</tt></td>\n", + " <td>0.0 J/m<sup>2</sup></td>\n", + " </tr>\n", + " <tr>\n", + " <td>Interfacial DMI constant </td><td><tt>Didmi</tt></td>\n", + " <td>0 J/m<sup>2</sup></td>\n", + " </tr>\n", + " <tr>\n", + " <td> </td><td></td>\n", + " <td> </td>\n", + " </tr>\n", + " <tr>\n", + " <th>Mesh parameter </th><th>Attribute name</th>\n", + " <th>Value</th>\n", + " </tr>\n", + " <tr>\n", + " <td>Mesh scaling</td><td><tt>scale</tt></td>\n", + " <td>1e-09</td>\n", + " </tr>\n", + " <tr>\n", + " <td>Number of nodes</td><td><tt>nx</tt></td>\n", + " <td>272</td>\n", + " </tr>\n", + " <tr>\n", + " <td>Number of boundary nodes</td><td><tt>nb</tt></td>\n", + " <td>105</td>\n", + " </tr>\n", + "\n", + " </table>\n", + " </p>" + ], + "text/plain": [ + "<tetrax.core.sample._WaveguideSampleFM at 0x7fcb88bcdd60>" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sample" + ] + }, + { + "cell_type": "markdown", + "id": "90927949", + "metadata": {}, + "source": [ + "Let's set an initial state and visualize it. " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "4d314731", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "7b0abae7e30d4235a217812c00f3d737", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Output()" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "sample.mag = tx.vectorfields.helical(sample.xyz, 60, 1)\n", + "sample.show()" + ] + }, + { + "cell_type": "markdown", + "id": "ce657f27", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "id": "da06b6c1", + "metadata": {}, + "source": [ + "Let's create an experiment, set a circular field of 80 mT and visualize it. Fields too must be given in SI units." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "5191b60c", + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "ac95a015dcd34ec88813f7d1011f4380", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Output()" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "exp = tx.create_experimental_setup(sample)\n", + "Bphi = tx.vectorfields.helical(sample.xyz, 90, 1) * 0.08\n", + "exp.Bext = Bphi\n", + "exp.show(scale=50)" + ] + }, + { + "cell_type": "markdown", + "id": "42475a32", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "id": "6f10dd3d", + "metadata": {}, + "source": [ + "Let's compute the equilibrium state and visualize it again." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "10ea0d6e", + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Minimizing in using 'L-BFGS-B' (tolerance 1e-13) ...\n", + "Current energy length density: -6.727272024843342e-11 J/m mx = 0.00 my = -0.01 mz = -0.00\n", + "Success!\n", + "\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "93f06cf1932f45a683782310fbd47f7b", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Output()" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "exp.relax(tol=1e-13,continue_with_least_squares=True)\n", + "sample.show()" + ] + }, + { + "cell_type": "markdown", + "id": "820b81b8", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "id": "af672816", + "metadata": {}, + "source": [ + "We can now compute the dispersion relation for a set of wave vectors and number of modes (not, the computation of each wavevector ```k``` is done parallel. ```num_cpu=-1``` will involve all available CPU cores into the dispersion computation):" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "d63a97de", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|██████████| 81/81 [00:46<00:00, 1.73it/s]\n" + ] + } + ], + "source": [ + "dispersion = exp.eigenmodes(num_cpus=-1,num_modes=10,kmin=-40e6,kmax=40e6,Nk=81)" + ] + }, + { + "cell_type": "markdown", + "id": "685b4ba2", + "metadata": {}, + "source": [ + "Let's plot the obtained dispersion, the first 5 or any number of modes. Here, we use transparency, thus the colour of the singlet solutions will be lighter compared to the dublets." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "f851e3a8", + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.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", + "\n", + "mpl.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_dpi_ratio', { dpi_ratio: fig.ratio });\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", + "\n", + "mpl.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", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\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", + " );\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('style', 'box-sizing: content-box;');\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; position: absolute; left: 0; top: 0; 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", + " 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", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\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 (fig.ws.readyState == 1 && 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", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.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", + " this.rubberband_canvas.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.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", + "\n", + "mpl.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", + "\n", + "mpl.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.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\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", + " evt.data.type = 'image/png';\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", + " evt.data\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 '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function 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", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * this.ratio;\n", + " var y = canvas_pos.y * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.which === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.which;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.which !== 17) {\n", + " value += 'ctrl+';\n", + " }\n", + " if (event.altKey && event.which !== 18) {\n", + " value += 'alt+';\n", + " }\n", + " if (event.shiftKey && event.which !== 16) {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k';\n", + " value += event.which.toString();\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.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", + "\n", + "mpl.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\n", + "var _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\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var 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.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", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(msg['content']['data']);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.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(\"<div id='\" + id + \"'></div>\");\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", + "\n", + "mpl.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", + " '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.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", + " '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n", + "};\n", + "\n", + "mpl.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", + "\n", + "mpl.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 = '<i class=\"fa ' + image + ' fa-lg\"></i>';\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 = '<i class=\"fa fa-power-off icon-remove icon-large\"></i>';\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", + "\n", + "mpl.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", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.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", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\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", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.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.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "<IPython.core.display.Javascript object>" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAoAAAAHgCAYAAAA10dzkAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAACgKADAAQAAAABAAAB4AAAAAAfNMscAABAAElEQVR4AeydB7QlRZ3/awiS8wgMzMgMURmCxAHXhKus6EHAvCYwrRFFDKz+jworEvTIrq6KcHZFWBUMKyvnrAlXScooOUoQZmCAgSEnCQLzf5/C36Pfnfve3Pdu33v7dn/qnH7dt1/fvl2fqq761q9+VTVt6UhIBglIQAISkIAEJCCBxhBYoTExNaISkIAEJCABCUhAApmAAtCMIAEJSEACEpCABBpGQAHYsAQ3uhKQgAQkIAEJSEABaB6QgAQkIAEJSEACDSOgAGxYghtdCUhAAhKQgAQkoAA0D0hAAhKQgAQkIIGGEVAANizBja4EJCABCUhAAhJQAJoHJCABCUhAAhKQQMMIKAAbluBGVwISkIAEJCABCSgAzQMSkIAEJCABCUigYQQUgA1LcKMrAQlIQAISkIAEFIDmAQlIQAISkIAEJNAwAgrAhiW40ZWABCQgAQlIQAIKQPOABCQgAQlIQAISaBgBBWDDEtzoSkACEpCABCQgAQWgeUACEpCABCQgAQk0jIACsGEJbnQlIAEJSEACEpCAAtA8IAEJSEACEpCABBpGQAHYsAQ3uhKQgAQkIAEJSEABaB6QgAQkIAEJSEACDSOgAGxYghtdCUhAAhKQgAQkoAA0D0hAAhKQgAQkIIGGEVAANizBja4EJCABCUhAAhJQAJoHJCABCUhAAhKQQMMIKAAbluBGVwISkIAEJCABCSgAzQMSkIAEJCABCUigYQQUgA1LcKMrAQlIQAISkIAEFIDmAQlIQAISkIAEJNAwAgrAhiW40ZWABCQgAQlIQAIKQPOABCQgAQlIQAISaBgBBWDDEtzoSkACEpCABCQgAQWgeUACEpCABCQgAQk0jIACsGEJbnQlIAEJSEACEpCAAtA8IAEJSEACEpCABBpGQAHYsAQ3uhKQgAQkIAEJSEABaB6QgAQkIAEJSEACDSOgAGxYghtdCUhAAhKQgAQkoAA0D0hAAhKQgAQkIIGGEVAANizBja4EJCABCUhAAhJQAJoHJCABCUhAAhKQQMMIKAAbluBGVwISkIAEJCABCSgAzQMSkIAEJCABCUigYQQUgA1LcKMrAQlIQAISkIAEFIDmAQlIQAISkIAEJNAwAgrAhiW40ZWABCQgAQlIQAIKQPOABCQgAQlIQAISaBgBBWDDEtzoSkACEpCABCQgAQWgeUACEpCABCQgAQk0jIACsGEJbnQlIAEJSEACEpCAAtA8IAEJSEACEpCABBpGQAHYsAQ3uhKQgAQkIAEJSEABaB6QgAQkIAEJSEACDSOgAGxYghtdCUhAAhKQgAQkoAA0D0hAAhKQgAQkIIGGEVAANizBja4EJCABCUhAAhJQAJoHJCABCUhAAhKQQMMIrNSw+JYa3aeeeirddtttaa211krTpk0r9d7eTAISkIAEJCCB3hBYunRpevDBB9Mmm2ySVlihmbYwBWAXeQvxN2vWrC7u4FclIAEJSEACEhgUgUWLFqWZM2cO6ucH+rsKwC7wY/kjkIHWXnvtLu7kVyUgAQlIQAIS6BeBBx54IBtwoh7v1+9W6XcUgF2kRnT7Iv4UgF2A9KsSkIAEJCCBARCIenwAPz3wn2xmx/fAsfsAEpCABCQgAQlIYHAEFICDY+8vS0ACEpCABCQggYEQUAAOBLs/KgEJSEACEpCABAZHQAE4OPb+sgQkIAEJSEACEhgIAQXgQLD7oxKQgAQkIAEJSGBwBBSAg2PvL0tAAhKQgAQkIIGBEFAADgS7PyoBCUhAAhKQgAQGR0ABODj2/rIEJCABCUhAAhIYCAEF4ECw+6MSkIAEJCABCUhgcAQUgINj7y9LQAISkIAEJCCBgRBQAA4Euz8qAQlIQAISkIAEBkdAATg49v6yBCQgAQlIQAISGAiBlQbyq/7ohAQWL16cbrnllrTeeuulddddN62zzjpp5ZVXnvA7/lMCEpCABCQgAQl0SkAB2CmpPl533XXXpSuvvDI961nPytsqq6ySheCGG26YRSGCkG3FFVfs41P5UxKQgAQkIAEJ1IWAArCCKbnqqqtmwff444+nRx55JD300EPp7rvvTjfeeOOoIEQUrr/++mn69OmjVsK1115bUVjB9PSRJCABCUhAAlUjMG3pSKjaQw3L8zzwwAPZEnf//fcnxFdZgST5y1/+ku6777683XXXXenOO+9Mjz76aEIUPvbYY+mpp57KP7fCCiuMsRSGKMRCSPexorCsVPE+EpCABCRQFwK9qr+HiY8CsIvU6mcGQhQ++OCDY0QhVsEQhQjDdqKQbmR8CVsthfoUdpHwflUCEpCABIaaQD/r76qCUgB2kTKDzkAIvqIoRBCOJwqnTZs2ailEFGIhRBSGPyGWQrqeuc4gAQlIQAISGASBMHbQs0Yde++996bNNtsszZo1q9THGXT9XWpkpngzfQCnCK4KX6P7NwQcLwihnSi85557Ri2FdC0jGos+hQhCLIJrrLHGqKUQQci911xzzcTvGCQgAQlIQAJlEnjiiSdyfRRij7oKlyfcnOjVYuMatrIFYJnxGNZ7KQCHNeXGee52opAWFQNJeMnwK6RFhU8hA0z++te/5peN/3Oe6WdWWmmlbC1EFDLYhC5kfAsRhGuttVb2K+S81sJxEsHTEpCABCQwSoA6CFGH1Y2Nugihx57z1EOIvSeffDJ/J3qsVltttWycKNPHfvShPEgKwAZkAl4mhBvbzJkzc4x5IRGAvIBsiD9eSIQgLyIvJP/nuiVLlmSxF5ZC9quvvnraYIMN8kCTEIXsEY8GCUhAAhJoJgFEHPVIq9h7+OGHR+sW6hjqFkIMZKQHirqFwYtF9yQMD5w3lE/A2rp8pkNxR0QhIo5txowZo8/MixkvLtZCTPKIw2ilMeiEl5vzWAuZi7AoDBGBCENabByz0Y3snIWjiD2QgAQkMPQEEHDhUoRbEfUGrkWtVj26byNE71L4nFNXRO8S59g0IgSt3u8VgL1nPFS/gJij9cUWAb9CWm9FYRjWQiyFbIhCruP8ggULstmeLmQ27smLzYvOHkGoMAy67iUgAQlUlwBCj4Y/Ii82jAJsnI/uW/ZFqx5lPwMLKf8p86PHKISe/uWDT3NHAXeRBggizNO0eMjUTQu88DCI1h8FApbB8C3k/2wIwwghCkMYIgQRhkVrIQUD/zdIQAISkEB/CCDeKLtpzLNRrkcvUAg9ynN6iVrL9OgFCp9x/MbDZ5w956sWml5/kx5aAKuWK4foeRBptOrYIlCIhLNvCMMYeFIsRDimEGEwCiucYPYvikP8QYoWQz4jDHEKdvBJ0HYvAQlIYHIEKHfp0Qmhx54ymq1YRrcKvSijKYcRdMVeHY5pxFNOO2vE5NJjkFcrAAdJv4a/jTjD7M/G2sURiq3LEIZYTrEYFgsdCiMKKHxJbr755uw7iDCMwoeChxYljsIURCEM2dMKNUhAAhKQQMqWuhB5CD7KXXppsHwh7vDNa9dLE2Vt9MREL01Y9PjM/7jOMNwETMHhTr+hefrioJNWYYjFkMKJwirEIQUVhVYUUIhE/k+4/fbb8z4shlFgYR2kkKI1GuIQYcjGtQYJSEACdSKAiKOcjI0yEoGHNS9ccYpCL+JOeRwN6/DTC6HHPkQee4VeUKvfXgFYvzQdqhgVLYbPfvazxzw74i9asLGPFiyiMQo2jmP+KEYm0wVBoRUbBV1RHIYoZAR0WA55DoMEJCCBqhGgHGS0bYg89jSU6UHhOMpBruO46J/H7AuUf/Sc0Ciml4TeE7YQeZxnc6aGqqV8759HAdh7xv7CFAlQcOFMzFYMdCdHgYgwjAKRVi+fo3uDwjB8Dfk+4hChFxbDEIgUjhSGWA8RhDE9DnuEI9cZJCABCfSCAOUZ5RRlWpRrUaYh8sJFhvIsxF5R5EWDNxq67CnHKDfpDeE4RJ4+1L1IweG9pzXb8KZdY58cEUehxlbsTgYIlsBiIRoFKeKQ4+hSpiAtWg4XL16ceYYoLO4pNMPJOURh7BWIjc2GRlwCHRFA4NEopVyiW7Yo9GIWhbDeFUUe34tQFHl02VI+Uf6Fy0uIPPaUTTZag5z7iQgoACei4/+GjgDdGFjz2FpDiMMQiOzD55B90XKIOOT/xZZ2WA/5jaJApECO30QQslEIc55jLIx2Mbemhp8lUA8ClBFY6RB3xY0GJwKPfTQ2Q+CxD7eVoBBlCha8EHEh8ihfOI6N/3OdQQLdEFAAdkPP7w4VgYnEIa1tCmkK62ihs6dLOQrxaKVTcMe1xVY6MCjEQyDGnnMU3BTiFNwhEhGIxU2ROFTZyYdtAAHeb8RdCLzYI/QoK2g4Uk5QJrSKu1aBVywPaBRSLuCTR+9CDFyLxiPlBWUF3zFIoFcEFIC9Iut9h4oA4ivEWHFew4gErfzovinuiwIxKgAKfsQilUVrJVC0IlK4FysFCv9o4RethxxTYbCnwlAoRqq4l8DUCCDs4h3lPaVBxz4EHuKOjXe9+F6H0GM/XuMPyxzvagg8/O/CFw9RF41A9r7PU0s/v1UOAQVgORy9S80J4IMT4qxdVMNSQIVR3LAOYCWIyoSKIyqRqID43BpCHLbuqVSKVoIQhwjE2KhUOKYiUiy2kvVznQkg1nDlQNAVtxB3Yd3nHeX9i/exuOcerSHew3j/ouHGu4hlH5EXwo5zsSnwWkn6uUoEFIBVSg2fZWgJILSi0B8vElQsURGFpSH2MZo5LA7FComKjM9Ff8T4DYRpVE6xL57jmaJiKgpEKqZ2m4IxyLofNAEaVeR7BF3rxjsRQq/Y4OJcvDu8L3Ec+9Y4Fd8VxB3vSFHcRaOPd4jGVrzjHLNxrUECw0pAATisKedzDx0BKpiYjmG8hw9LIhVcUSzymYoOSyJWDP6HoIyKjcqOz1zHcTuxiLijwipWehwXPyMKw6pIZcdnLIlsxeM4F3vuoXgcL1WbfT6EHBa32BBqccw+BF4x33PM/0LIte7J++Pl82KeJo+GWCP/c4ygQ9yRx/ncuiEEuYdBAnUmoACsc+oat6Ej0IklkUhRqYYFhIoyKs44h1gMochxCMWoNKk4uZZ9bO1gUQl2siFuw6IYlkYqXs7HnuPYOEdlHPs4z28pJNulRP/PhXAjz9C4GG9DpPG/oqAjP0ZebM1nkd/YF/Mjn/nNdqHYcCGPkF84F+fJcyHsQtRFPoz/xWe+b5CABEYGLQpBAhIYPgKIpKjQlvf0VKqtVhYq5eJGhY1QxLIYgrJYURePqeypuLkv58cLPCNbUUDG59iH4Ct+DjEYwpF9HEelH3u+33ocvxfiID4X9/F7sed/HBNiP168+nU+xFBwDoFU3BfTpXgcwqrdnnPFrVW8FYVe3JNnKD5H8Rlaz0/Ep5gGHJNG0QiI/3EuLHKIucjn0cBgz7ninu8aJCCByRGorQA8/vjjE9vChQszkblz56bPfe5zaZ999smfKbSOOOKIdOKJJ+YFsufNm5e+8Y1vJK4zSKBOBBA0UXl2Gi/eDwRiCMew7sS54mfOhWgMUVkUBa3HCIzWc3zuJBAXtqjw2ce51j33m+hc8f/F47h/63fbfY7vFfccF0PErbjneKINgVa8nvvF9eMdF/+PQCPEPv4Xwi0+s+8kFJkEc/aI9fhf8XwIuxBvIdgQe+TFEPXFz5FH+S73NEhAAr0lUFsBOHPmzHTMMcekLbfcMhM8+eST03777ZcuueSSLPK+9KUvpeOOOy595zvfSVtvvXU68sgj0yte8Yp07bXXtp1EuLfJ4N0lUC0CVMBReU/2yRAVYVUKaxKCMY5b94idEJTsEZHF74cY4r7tBExRzLT+n2cv/r/4ebLxqtL1pE+IpDhu97n4v+JxiLXiOYQXgg5Rxr5VqMX/45oQeXyOc7FXxFUpt/gsEmhPYNpI4dhZE7D994fq7Prrr5++/OUvp3e9611pk002SYccckg67LDDchywYGy00Ubp2GOPTe973/s6ihcTBLMUD+s1Ms+TQQISKJ8ARRQiEFHIHpHHvrjFOfatx3GuuI97xjnuxbn4HHvOxXliFp9jH+eKe46LoSjMOF8UXe2OEWfFjWsQVCGqYl+8pvWYazgX+zjmc3HjPKKNc/GcxWf3WAJ1JWD93RAfQAr3H/3oR3kE5Z577pkWLFiQbr/99rT33nuP5m2sHS95yUvS73//+44F4OiXPZCABHpGAGESVqae/Yg3loAEJNAwArXtAiYdr7jiioTgw7Gd6TdOP/30tO2222aRx/+x+BUDn2+66abiqTHH4ecUJ2lBGCQgAQlIQAISkMCwEaj10KltttkmXXrppWn+/PnpAx/4QDrwwAPT1VdfPZpGrV0edOu0nhu9eOTg6KOPzl2+dPuyzZo1q/hvjyUgAQlIQAISkMBQEKi1AMSJmUEgu+66axZvO+64Y/rqV7+aNt5445w4dAMXw5IlS5axChb//+lPfzr7++Hzx7Zo0aLivz2WgAQkIAEJSEACQ0Gg1gKwNQWw8NGNO2fOnCwCzzzzzNFLGHl49tlnpxe84AWj51oP8BNksEdxa73GzxKQgAQkIAEJSKDqBGrrA/iZz3wmz/lHN+2DDz6YTjvttHTWWWelX/ziF7mblxHARx11VNpqq63yxjHLA73lLW+pepr5fBKQgAQkIAEJSKArArUVgHfccUd6+9vfnhYvXpz99XbYYYcs/pjrj/CpT30qr3zwwQ9+cHQi6F/96lfOAdhVdvLLEpCABCQgAQkMA4FGzQNYdoI4j1DZRL2fBCQgAQlIoPcErL9TapQPYO+zlL8gAQlIQAISkIAEqk9AAVj9NPIJJSABCUhAAhKQQKkEFICl4vRmEpCABCQgAQlIoPoEFIDVTyOfUAISkIAEJCABCZRKQAFYKk5vJgEJSEACEpCABKpPQAFY/TTyCSUgAQlIQAISkECpBBSApeL0ZhKQgAQkIAEJSKD6BBSA1U8jn1ACEpCABCQgAQmUSkABWCpObyYBCUhAAhKQgASqT0ABWP008gklIAEJSEACEpBAqQQUgKXi9GYSkIAEJCABCUig+gQUgNVPI59QAhKQgAQkIAEJlEpAAVgqTm8mAQlIQAISkIAEqk9AAVj9NPIJJSABCUhAAhKQQKkEFICl4vRmEpCABCQgAQlIoPoEFIDVTyOfUAISkIAEJCABCZRKQAFYKk5vJgEJSEACEpCABKpPQAFY/TTyCSUgAQlIQAISkECpBBSApeL0ZhKQgAQkIAEJSKD6BBSA1U8jn1ACEpCABCQgAQmUSkABWCpObyYBCUhAAhKQgASqT0ABWP008gklIAEJSEACEpBAqQQUgKXi9GYSkIAEJCABCUig+gQUgNVPI59QAhKQgAQkIAEJlEpAAVgqTm8mAQlIQAISkIAEqk9AAVj9NPIJJSABCUhAAhKQQKkEFICl4vRmEpCABCQgAQlIoPoEFIDVTyOfUAISkIAEJCABCZRKQAFYKk5vJgEJSEACEpCABKpPQAFY/TTyCSUgAQlIQAISkECpBBSApeL0ZhKQgAQkIAEJSKD6BBSA1U8jn1ACEpCABCQgAQmUSkABWCpObyYBCUhAAhKQgASqT0ABWP008gklIAEJSEACEpBAqQQUgKXi9GYSkIAEJCABCUig+gQUgNVPI59QAhKQgAQkIAEJlEpAAVgqTm8mAQlIQAISkIAEqk9AAVj9NPIJJSABCUhAAhKQQKkEFICl4vRmEpCABCQgAQlIoPoEFIDVTyOfUAISkIAEJCABCZRKQAFYKk5vJgEJSEACEpCABKpPQAFY/TTyCSUgAQlIQAISkECpBBSApeL0ZhKQgAQkIAEJSKD6BBSA1U8jn1ACEpCABCQgAQmUSkABWCpObyYBCUhAAhKQgASqT0ABWP008gklIAEJSEACEpBAqQQUgKXi9GYSkIAEJCABCUig+gQUgNVPI59QAhKQgAQkIAEJlEpAAVgqTm8mAQlIQAISkIAEqk9AAVj9NPIJJSABCUhAAhKQQKkEaisAjz766LTbbrultdZaK2244YZp//33T9dee+0YeAcddFCaNm3amG2PPfYYc40fJCABCUhAAhKQQN0I1FYAnn322elDH/pQmj9/fjrzzDPTE088kfbee+/08MMPj0nDV77ylWnx4sWj289+9rMx//eDBCQgAQlIQAISqBuBleoWoYjPL37xizjM+5NOOilbAi+66KL04he/ePR/q6yyStp4441HP3sgAQlIQAISkIAE6k6gthbA1oS7//7786n1119/zL/OOuusLAy33nrr9N73vjctWbJkzP+LHx577LH0wAMPjNmK//dYAhKQgAQkIAEJDAOBaUtHwjA8aDfPSBT322+/dO+996Zzzz139FY/+MEP0pprrpk222yztGDBgvTZz342dxVjJcQy2BoOP/zwdMQRR7SeTojLtddee5nznpCABCQgAQlIoHoEMOass846ja6/GyEA8QX83//933TeeeelmTNnjpsT8QVEDJ522mnpta997TLXYQFki0AGmjVrVqMzULBwLwEJSEACEhgWAgrAlGrrAxiZ8OCDD05nnHFGOueccyYUf1w/Y8aMLACvv/76+PqYPVbBdpbBMRf5QQISkIAEJCABCVScQG0FIN2+iL/TTz894ec3Z86c5SbF3XffnRYtWpSF4HIv9gIJSEACEpCABCQwpARqOwiEbt/vfve76fvf/36eC/D2229PbI888khOqoceeih94hOfSOeff35auHBhFon77rtvmj59ejrggAOGNDl9bAlIQAISkIAEJLB8ArX1AWSC53aB6WCYABohyOTQl1xySbrvvvuy1W+vvfZKX/jCF7JfX7vvtp7Th6CViJ8lIAEJSEAC1Sdg/V1jH8DlDW5ebbXV0i9/+cvq51KfUAISkIAEJCABCZRMoLZdwCVz8nYSkIAEJCABCUigNgQUgLVJSiMiAQlIQAISkIAEOiOgAOyMk1dJQAISkIAEJCCB2hBQANYmKY2IBCQgAQlIQAIS6IyAArAzTl4lAQlIQAISkIAEakNAAVibpDQiEpCABCQgAQlIoDMCCsDOOHmVBCQgAQlIQAISqA0BBWBtktKISEACEpCABCQggc4IKAA74+RVEpCABCQgAQlIoDYEFIC1SUojIgEJSEACEpCABDojoADsjJNXSUACEpCABCQggdoQUADWJimNiAQkIAEJSEACEuiMgAKwM05eJQEJSEACEpCABGpDQAFYwaS855570p///Of00EMPpaVLl1bwCX0kCUhAAhKQgASGmcBKw/zwdX32q6++Ol122WVpxRVXTGussUZ69rOfnTbddNO8rbvuummFFdTtdU174yUBCUhAAhLoB4HKCcBFixalhQsXpr/85S9Z+MydOzetssoq/WBRqd8gzo899lh64IEH8nbDDTekadOmpdVWWy2tv/76aeONN04zZ85M06dPbySfSiWWDyMBCUhAAhIYMgLTRroYB97HeNNNN6Vvfetb6dRTT00IwOIjPetZz0ovetGL0j/90z+l173udZWyfiHO1llnnXT//fentddeu9Skf+KJJ9Ldd9+dbrnllrR48eJ01113pUceeSQ99dRTY34HPvz2hhtumDbZZJMsDPmMWDRIQAISkIAEJLAsgV7W38v+WjXPDFwAfvSjH00nnXRS2nvvvdNrXvOatPvuu+euTixd+MJdeeWV6dxzz83icKWVVsrX7rbbbpWg2c8MhChGaN522215u/POO7NlEKFYDHQPr7766mOshBtssEFCKBokIAEJSEACw0QgDEJlGzX6WX9XlffABeAnP/nJ9KlPfSp39y4P0s9+9rPcNfz6179+eZf25f+DzkBYBBGCWAlvv/32dO+99+Zu43hhAgLib6211spWwhkzZmQrIZZLfQmDkHsJSEACEqgCgSeffDIbf2699dbc+0Udt9NOO6Xtt9++1McbdP1damSmeLOBC8ApPnclvla1DMSLc99996V4ceg2ZiRxOyvhqquumtZbb73RrmO6kLG6lt3KqkRC+RASkIAEJFBJAhgylixZkg0Zd9xxR1tDxuabb5722WefUp+/avV3qZHr8GaVEoD/8i//kl74wheml73sZWMe/+GHH05f+cpX0uc+97kx5wf9oeoZCEvgo48+Omol5CWjW53BJa2+hHSv03VMd/FGG22UsBRy3MQBOIPOV/6+BCQggToSwEiBKxNGCtyZJjJSMAMGgxypi2bPnp0NFmUyqXr9XWZcx7tXpQQgXZIrr7xyOvroo9Ohhx46+sy0ChjgQOapUhjGDITw4wWky5jBJeFL+Pjjjy+DlrRYc80180uIKGTkMSOQOW+QgAQkIAEJjEcAAwSzeVB/I/ioa8ZzU6JOYYozpjxjdgvqGuqeXvZIDWP9PR7rqZ6v3DQwp5xySvrwhz+cLr/88nTiiSc6eGGqKTvO9xDZdP2yPe95z8tX/fWvf82WQVpkvKy0yrC6cp4Xlu3666/P14Y/IS2zEIW8uIrCcYB7WgISkEADCNCzxMwV1CMYGOhtQgC2Gm5ioCI9TMXpzKxD+p9JKmcBJOM8+OCDad99980tgtNPPz1T0QLYv8wRXccIQdIDUTjey0wLjReXQSa80PgS8lIjMB153L8085ckIAEJ9IsAfuXUCfQihdEAf3OMBq0BNyIGHVI3xCBE6oteWvdan6HdZy2AKVXKAhgZYosttkjz589Pb3zjG9Ouu+6a5whsl4Ce6w0B0oEBIbNmzcobvxLmfEQhLz3+hFgGceCl+5iWH9t1112XHwrxhwk/BpqEKGTwSaRzb57eu0pAAhKQQFkEEHuU9WEMoJxHPLVzG8KXPNyGKPMx3FAHcN5QPQKVtADSUiDgr3bIIYek448/Ph+3mpIHjbPpLYgQhfh2UDiEjweisHXkMWmFpRBhSYEQXcjscfZ1SppB52Z/XwISaDoBLHjMJNEq9jhPeV8MLFUac86GdQ8fPhr5wxCaXn+TRpWS5UwIjak4AqLga1/7Wp4D6JxzzonT7itCAEse4o1t9sgoLQKFBAKQViKFSFgK8QWhEGHjxWP1FwKFSHQRMMCEAgTfQlYzsQs5I/KPBCQggdIJ4LNHN2403jnG/aqdZY9yGmEXZTRduYg+pw4rPVn6esNKWQD7GvMSfswWROcQmY6GbgT8RWI6Ggqbdi1LhCXWQoQlA0zCt5A93QtaCzvn7pUSkECzCdAoxz8vGuW48WDli0Z5Kx3EHsKOsheRR1cue6x9dXLfsf6uiAUQK9/yAhnv4IMPXt5l/r+iBGg90mpki0A3MVPS0HWMKKSA4jNikVYoG6JxwYIF+SsIP6yFOBDTjRzCkIKqboVTMHIvAQlIoFMClJmUoZSn0dDmc7u5X7lnzP9a9NXGLUfLXqfEh/u6SlgA58yZM4biokWLslAoOo4iAG+88cYx1w36gy2I8lMg/ArpjsBaiCjkmGlpKNxa/VB4AvIJ3cV0G4cwpCsZYWhBVn4aeUcJSGCwBPCHpweF8rHYeI7pu1qfjvozBuZFN25xYF7r9U34bP2dUiUEYGtmw8Jz2WWXJZZ/qXIwA/UvdbAWwpsCD4thWAvpxuB/nQhDxCHCED9TB570L+38JQlIYGoEEHqIOhrBUe7RfRtTrrSu6MSvhL8eDWDEHt23bDSQi0aVqT1Rfb5l/V2RLuD6ZClj0isCFFwUZmxbbbXV6M/QtUFLmFYwvi10GfNiMxAF/0LEISIRR+cIFJC0hhGBiMGwGnJvGh9OSBqk3EtAAv0gQDlFOUb5RTmG4Fue0MMlBtcayizEHo1bhB7lmT0f/Ui14f+NSo0CHn6cxqDfBPAJZMNvJQLWQLqLEYIUpu2EIQKRjf9FoJskpqqhUC2KQ47xM0Q8GiQgAQlMlgDlEg3W6MlA5EWDNbpu2/VkhO9zcVAcYi8arA6Km2xKeH0QUAAGCfe1IYCQQxRSSLIVA8KQljaFb/gXUiBHAcz/2XCcvuWWW0a/ivBDHCICi+IwCmHF4SgqDyTQaAL0PNBFiwWPMoY95QnlDgIQa1+7UBR6NDgZ5EbDNsoYG5/tqHmuGwKVEIBUwMVABc4L1HoeHwaDBLohQNcvBStbsSsZXxpEIAV1WAyj0GZUMoU6ezbEYzFQMNNFHeIwumQouMmznEc8kq8NEpDAcBPASkd5QHmBuIvuWuor6q0oL9pZ84h5DFqLhiTlRAg9fZOHO28M29NXYhAILZ9i5ciL0+6zK4EMW/aqx/NiEYwWfbGwL7boxyvsydsU+FgkKdyZx5DWPRt+O3zGX4drDBKQQDUI0CDERYT3noYg7z0Cjw3hR5mACJzovaexSeOPRmBY9KJRiO9esY6rRqyb9RSkJelC+jbVuFSJWue3v/1ts3KesR0qAhTkFNxsxZHpFP5UBGEJwJ+HwiQsAVQgdPdwDRuCsTWE9ZAKgcoCQYhlgAKpKBC1ILaS87MEpk4gBB4DxHhnseTx3vKOIvp4dzE4jNddyy/TuAuRFw07BmBQTiAstOZNPX38Zn8IVMIC2J+olv8rtiDKZ1qnO1J5UMFQqYQ4pLJBMIYVgWvGsyLAIiyIMTiFSoUNgRhCEeEYVkStCnXKQcZlKgSiYVa04EWjDHHHO9mJwONdooGGyCta72mYIfR4/xR5U0mhanzH+rui08BcddVVufUV2YSXcO7cufHRvQSGggDdugg1tk033XTMM0clVRSIYYFAHHIeq2HRgsj54qjluCHvBxsiMSyJYU3EMsHv85n/sdndHOTcDxsBrHL42IW4C4tdCLsQd7w3XNtunryIMwKPd6HYuELUIfCw4IUVD/cNG1ZBzX2dCFTCAnjuueemQw89NF1wwQWZLS8hL3JYRnj5fvnLX6aXv/zllWJvC6JSyVGrhyHv42NERYfwK3YtR2UXzubLq+gAE9aMolBEDGI5RByGhQPByHkqPTauN0iglwQQaTR2GCEb+T3yeDSGOE9+5zquR+AtL0TDKLppyePULWw0ihB40TBS4C2PZv3+b/1dEQvgN7/5zfT2t799TA7DL3CzzTbLIpC1go8//vjKCcAxD+wHCZRIgAqJiouNimqTTTZZ5u5hRaRipMLEGhIWESpOKk02Kk1EIhsVJxUt148X6HZmC7HIM4QoRDDGRuUZFWg8K9YUvmtoJgHyJPmMxkuIOhrzxY38ykbeJC9ybeTPiSx2QZR3A8sd+ZNGCnkzGjEh7sLqHa4R8V33EpDAMwQqMQgEy99HP/rRZ55q5GjmzJlZAHIScfjqV796zP/9IIGmE6AiDEsdInG8QOVKRUulS0VMyzcsK+yLlXFUxAjFEIvj3TfO8xxF0UjlHIKQfVTS8azsQ0QiGNn4TmzczzA4Aog40h5hFhtiLoRb7MlTbCH0QszxXYRcbJ3GhDxEXigKO/JJiLsYIMVnziP8bGx0StfrJLAsgUoIwFtvvTXNmDFj9OlOPvnkxELVERhVxYSaBglIYPIEqFCpNNl4l8YL0e0clT0WGgRiiMWo+NlHxY9gjIo+xMJ49293PsRj7KnQQwiyD3EYQrHdHpEZ5+O7xJmN+8U+joddYIaVLbizD+HOcYh30jHSpHgc51r38b1imsZvtEu75Z2DO+kC92gQhPgPq12IO7pnw8rMtcOeRstj4/8lUAUClRCAmO0XLFgwavF77WtfO4YN/8Okb5CABHpHgEo3KmqsLcsLRUtRWIGwMCIc24lFrmEL6xLfD4GB6CgrEI/lbYgSrglxyHGcYx9bu/vwPwL/a7fnHHGLEMfsi8fEPc6xj8/BpN3nOFf8Xrvj+O1u9hF3RHXwCKEd+SSsuoi3EHVhneN/XBcisJtn8bsSkED5BCohAOfNm5dOOeWU9NKXvrRtDL/zne8krjFIQALVIYBACEGAJWcyASETQjAsT4jD8FkMC2OIxrBWxbWxD8sX+3ZCKATTZJ5t2K4NocYeoVb8jMCNLSyqYZmLtAsxxz4EXezjO2FZ5d4GCUigHgQqIQAZAcwIX5bn+uQnP5k23HDDTHfJkiXp2GOPTd/97nfTr371q3oQNxYSkEAWKiE8ysCB+CuKQY4Rf2xxHhGJcORz7Iv/j+/EvUI8xp7zccwz8zn2cZxP/O1PiKV2e86FWGvdh1Uy9vwfAVYUcvE5rGtxbVwT+/jt4nN5LAEJSAAClZgGhgdhJPDHPvaxXDDT3UvBxdQXFHRf+cpX0oc//GEuq1RwGHmlksOHkYAEJCABCXREwPq7QgKQFFu0aFH68Y9/nK6//vqcgFtttVV6/etfn2bNmtVRgvb7IjNQv4n7exKQgAQkIIHuCVh/V0wAdp+k/b2DGai/vP01CUhAAhKQQBkErL9HlhotA2Q39zj//PM7/jrTUbBMnEECEpCABCQgAQlIYOoEBi4A3/GOd6RXvOIV6Yc//OG4qxNcffXV6TOf+Uzacsst08UXX9xRbI8++ui022675WV/GFSy//77p2uvvXbMd3HcPvzww/MqC0xdwChkBeYYRH6QgAQkIAEJSKCGBAYuABF3++23X/rc5z6X1ltvvTR37twsCPfdd9/0whe+ME2fPj3tsssu6aabbkpnnnnmMkvGjZcmZ599dvrQhz6U5s+fn7/HqL+99947T2ob3/nSl76UjjvuuPT1r389r0PM5NOIUZbTMkhAAhKQgAQkIIG6EqjMKGAAY90799xz08KFC/N8YIi/nXbaKe21114TrmDQSeLceeedeXoZhOGLX/ziPIUD66secsgh6bDDDsu3YO6xjTbaKE898773vW+5t9WHYLmIvEACEpCABCRQOQLW3ylVYh7AyBk777xzYutFYEoZQiyFxeoit99+e7YKxu8x+elLXvKS9Pvf/z61E4AIRLYIZCCDBCQgAQlIQAISGDYCA+8C7gcwfP2YbJou5e222y7/JOKPgMWvGPgc/yue5xi/wnXWWWd0q+r0NK3P7WcJSEACEpCABCRQJNAIAcgk0pdffnk69dRTi3HPx60z5SMWW8/Flz796U/nyamxJrIxb6FBAhKQgAQkIAEJDBuBSnUB9wLewQcfnM4444x0zjnnpJkzZ47+BAM+CFj7ZsyYMXqe5edarYLxz1gfMz67l4AEJCABCUhAAsNIoLYWQCx5WP5+8pOfpN/85jdpzpw5Y9KHz4hARhZHYOF5Bom84AUviFPuJSABCUhAAhKQQO0IVMoCyMCMVqE2VeJMAfP9738//fSnP81zAYZfHz58zPlHNy8jgI866qjEknNsHK+++urpLW95y1R/1u9JQAISkIAEJCCByhOo1DQwK664Yp6i5d3vfndeA3jVVVedMsDx/PhOOumkdNBBB+X7YiU84ogj0gknnJDuvffeNG/evPSNb3xjdKDI8n7cYeTLI+T/JSABCUhAAtUjYP1dsbWAr7zyyvTtb387fe9738vTrbzpTW9KiMHdd9+9erln5InMQJVMFh9KAhKQgAQkMCEB6+8KrAVcTCGmaGFljltvvTVhqaPblqlbWB2E80zmbJCABCQgAQlIQAIS6I5AJQeBrLTSSumAAw7I6wMfe+yx6YYbbkif+MQn8ihe1g5evHhxd7H22xKQgAQkIAEJSKDBBCopAC+88ML0wQ9+ME/PguUP8YcIZDQv1kHWDjZIQAISkIAEJCABCUyNQKVGASP26Pq99tpr06te9ap0yimn5P0KKzytUxkhzICN5z73uVOLrd+SgAQkIAEJSEACEqjWWsDHH398ete73pXe+c535jn62qXPc57znPSf//mf7f7lOQlIQAISkIAEJCCBDghUahqYDp63Upc4iqhSyeHDSEACEpCABDoiYP1dsVHAdP/+6Ec/WibxOHfyyScvc94TEpCABCQgAQlIQAKTJ1CpQSDHHHNMmj59+jKx2HDDDfMqHcv8wxMSkIAEJCABCUhAApMmUCkBeNNNN7VdCm6zzTZLN99886Qj5xckIAEJSEACEpCABJYlUCkBiKXv8ssvX+YpL7vssrTBBhssc94TEpCABCQgAQlIQAKTJ1ApAfjmN785feQjH0m//e1v05NPPpk35v776Ec/mvifQQISkIAEJCABCUigewKVmgfwyCOPTHQD//3f/31iNRDCU089lVj946ijjuo+tt5BAhKQgAQkIAEJSCBVchqY6667LtHtu9pqq6Xtt98+4QNYxeAw8iqmis8kAQlIQAISmJiA9Xeq1kTQkVxbb711YjNIQAISkIAEJCABCZRPoFJdwPj9fec730n/93//l5YsWZK7f4tRxh/QIAEJSEACEpCABCTQHYFKCUAGeyAAX/3qV6ftttsuTZs2rbvY+W0JSEACEpCABCQggWUIVEoAnnbaaemHP/xhetWrXrXMg3pCAhKQgAQkIAEJSKAcApWaBuZZz3pW2nLLLcuJmXeRgAQkIAEJSEACEmhLoFIC8OMf/3j66le/mpYuXdr2YT0pAQlIQAISkIAEJNA9gUp1AZ933nl5Euif//znae7cuWnllVceE8Of/OQnYz77QQISkIAEJCABCUhg8gQqJQDXXXfddMABB0w+Fn5DAhKQgAQkIAEJSKBjApUSgCeddFLHD+6FEpCABCQgAQlIQAJTI1ApH0Ci8MQTT6Rf//rX6YQTTkgPPvhgjtVtt92WHnrooanF0G9JQAISkIAEJCABCYwhUCkLIOsAv/KVr0w333xzeuyxx9IrXvGKtNZaa6UvfelL6dFHH03f+ta3xjy8HyQgAQlIQAISkIAEJk+gUhZAJoLedddd07333pvXAY7o4BfI6iAGCUhAAhKQgAQkIIHuCVTKAsgo4N/97neJ+QCLYbPNNku33npr8ZTHEpCABCQgAQnUnMD999+f1lhjjbTSSpWSK7WgXimiTz31VGI94NZwyy235K7g1vN+loAEJCABCUigXgTQAfj+L1y4MN13331pxx13TM95znPqFckKxKZSAhCfv3/7t39LJ554YkbDWsAM/vj85z/v8nAVyCw+ggQkIAEJSKBXBBj4yVgAjD5//etf88+ssMIK6ZFHHunVTzb6vtNGVt2ozLIbKP699torrbjiiun666/P/oDsp0+fns4555y04YYbViqxHnjggbTOOuskTNRrr712pZ7Nh5GABCQgAQlUnQA9f4sXL87C7+677x593NVXXz3h/jVr1qy0yiqrjJ4v68D6O6VKWQA32WSTdOmll6ZTTz01XXzxxYmM8e53vzu99a1vHTMopKwM4H0kIAEJSEACEug/Aax6WPti1g+egF6/jTfeOAs/DD98NvSOQKUsgL2LZm/ubAuiN1y9qwQkIAEJ1I8AHY533XVX9u274447UnRArrrqqln04efHcT+C9XfFLICnnHLKhOn+jne8Y8L/+08JSEACEpCABKpFAH8+/PoY1FFc1AEr3+zZs9NGG22U8PUz9JdApSyA66233pjYk2n+8pe/5Glh8Ae45557xvx/0B961YIgnrfffnvafPPN+9YaGjRLf18CEpCABOpFgEEdiD7EH6t8EZjOBb8+hN+aa645sAj3qv4eWISm8MOV8gFkAujWwCCQD3zgA+mTn/xk679q+/nqq69Ol112WTrrrLPS+uuvn2bOnJm22GKL7BuhT0Rtk92ISUACEhh6AnTr0r27YMGC3N0bEWJVrzlz5qRNN910UnP6Pf7449kXcOWVV45buS+JQKUEYLs4bbXVVumYY45Jb3vb29I111zT7pLanWMUNH4QLH/HqCg2BCEjoTCVMzIKQcjkmAYJSEACEpDAoAnQY7do0aIs/Oi5I8SgDoQfxoxODRhYDq+77rp8rzvvvDPtscceaaeddhp0FGv3+5UXgBBHEDFFTFPCnnvumTP8kiVL0p///OdsPqdbmPWRGTHFxqopTEHDyGm6irESwskgAQlIQAIS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//77p2uvvXbMIw9zHI8//vi0ww47jE7Yveeee6af//zno/Eb5riNRqJwQHqSTw855JDRs3WL42jEBnygABxwAoz385/61Kdygdz6/yeffDK9+tWvTg8//HA677zz0mmnnZb++7//O3384x9vvbSyn/faa6/0wx/+MBfSPPsNN9yQXv/6148+7zDH8ZprrkksEXjCCSfkSvRf//Vf07e+9a30mc98phbxIxI0Nt7whjekD3zgA6NxKh4Mc/oRjx/84Ae58vl//+//pUsuuSS96EUvSvvss09C1A5joKzYcccd09e//vW2j/+lL30pHXfccfn/F1xwQdp4443TK17xirxMVtsvVOzk2WefnT70oQ+l+fPnpzPPPDM98cQTae+9985lZDzqMMeR1aaOOeaYdOGFF+btZS97Wdpvv/1GRfowxy3SJ/bkvxNPPDEL3jjHvk5xLMZr4McjytpQMQI/+9nPlj73uc9dOtIKZ53mpSOV0OgT8r+RdQuX3nrrraPnTj311KWrrLLK0vvvv3/03DAd/PSnP1060uJbOiIs8mPXLY4jhdfSEYvgaJLUJX4nnXTS0pGllEbjFQfDHr/dd9996fvf//6ITt7zPv7zP//zmHPD+IHy5PTTTx999JHGytIRwbd0RGCMnnv00Udzuo40XEbPDdPBkiVLcrk5IgzzY9cxjuutt97S//iP/1hap7iNrMu7dKuttlo6IuKXvuQlL1n60Y9+tLbpV5X3SQvgwCX42Ae444470nvf+970X//1X7mLbex/Uzr//PPTdtttN8Y6+A//8A+JLuOLLrqo9fLKf77nnnvS9773vdyluPLKK+fnrVscR4R5Wn/99UfTom7xG43Y3w6GOX5YN3mPsCAVA59///vfF0/V4njBggXp9ttvHxPfkcZkGqmAhza+vG+EeOfqFEes6/T6YNWlK7hOccOKS+/Wy1/+8jHvVp3iOCZiFfigAKxAIsQjjLQK0kEHHZRGrA9p1113jdNj9hTWG2200ZhzI63B9KxnPSsX5GP+UeEPhx12WPZh3GCDDXLX2ogVcPRp6xJHIkT39r//+7/nNI0I1il+Eafifpjjd9dddyUq2dZ3jM/Eq24h4lSX+FKGHnrooemFL3xhbiiTXnWI4xVXXJHWXHPNhDinfhix4qZtt922FnEjjRC1F198ccL/rzXUIf1a41SVzwrAPqQEDtY4tU604d+BUHjggQfSpz/96Qmfivu0Bgq+dudbr+vV507jGL//yU9+MvtX/epXv8pLNb3jHe9IxCFCu7gMMo6TjR/xuO2229IrX/nK7C/3nve8J6KW93WI35gItXyoWvxaHm+5H1uff5B5b7kPW8IFdYnvhz/84XT55ZenEbeYZagMcxy32WabxGBA/BzxvT3wwAPT1VdfPRrHYY7bokWL0kh3b/rud7+bBwaORqrlYJjj2BKVynxcqTJPUuMHoVB685vfPGEMZ8+enY488sj8gtPKKwasgW9961vTySefnB20//CHPxT/ne69997017/+dRmrxZiLevyh0zjGYzB6mW3rrbdOz3ve89KsWbNy3OnWwAm9anGcbPwQfwx2IT44NRdDHeJXjE/rcRXj1/qM430mT7K2algd4roRv7KBvl/xHGXvSSsC8WVEfoRhjO/BBx+czjjjjMSoZwZORKhDHOnh2XLLLXOUqA8YLPHVr3410ZNCGOb0w+WC/MZo+whY4UlHBi7FiO5hjmPEq3L7kZatoSIEbrrppqUjpv7R7Ze//GV2Zv7xj3+8dKSVlJ8yHOxHBMboU4+Yz4d6EMjI6Mocz9/+9re1iOMtt9ySnZlHRP/SkRGJo+kUB3VJw+UNAhnWPMogkBErSyRX3o80Umo9COTYY48dje+IP/FQDQJhIMSI/9jSkWlsll533XWj8fj/7Z13rBXFF8cPSIzYG9gLYtRgFMQWGzFiQRPB2CKIBlAQjSX2Eo0aNfb2x4stqCQoogYb2CXGhi02JJBYYwn23qIx8zuf4282e68X5Qn3vru870nu293Z3dmZz7zyfefMmc07OVGiyn3MfclbzwRO7gUskkCq3DePehV/8/LfPxe5afTo0VG+NI5fHseu3hJ2k7UpAZ/8GsKonAWMoPAkkDR06NDkcybSk08+mfy/3eQeqjbtRW2z3LOXPNQdmc2+Zl6aNWtW8vk6ydfLS2QfYlXuI9nZ/p964hc0QnDBggXFJ5Oocv/oA/+o8D154YUXJp+XFPsck8WHVb1//EPlCUlp0qRJycNsydcjS77mZuL7tYrGuDA+fNwDkXzJl9hnHDEygMnmnj59evzBHTlyZHJvYOIPcxUMsU77n3766eJnjZ+7X375pWh+lfvoU4KSe8MSfw88vJ18SalYCcKnz0T/qty3YoDqdspZwJxaGvtY1+UuOZQA7BLsi/bQRgKQO/nF7dlSyRdtTZ7pFuIvi6dFq7nrruIXmIdGo90sXeOh71hyA7FUtqr2Ea8Yf2QbfZaG/tEHPA+N+pc9uFxT1fGj7VhHR0faaKONkofe0uDBg1NeUuSvs9X6yrg0Gi/GEcPD4ouVx3Iw/EwOGTIkhGCcrMCXRn2jjJ/FbFXu47hx44rvxT59+sQ//1n80b8q9y2PT/22XgAujX2s73NXHPfgof7DIhMBERABERABERABEegmBJQF3E0GWt0UAREQAREQAREQgUxAAjCT0FYEREAEREAEREAEugkBCcBuMtDqpgiIgAiIgAiIgAhkAhKAmYS2IiACIiACIiACItBNCEgAdpOBVjdFQAREQAREQAREIBOQAMwktBUBERABERABERCBbkJAArCbDLS6KQIiIAIiIAIiIAKZgARgJqGtCIiACDSBwO+//x7vcX3++eebULvZ7rvvbv62kqbU/U+V+ivjbMMNNzTe5SoTARGoHgEJwOqNmVosAv+ZwI033mgrrbSS+evaijp++ukn81ef2W677VaUsfPss89ajx49zN+vWlO+NB20QjzdfPPN5m8VsV122aWl6Ogb490s87eG2GmnnWZnnnlmsx6hekVABJpIQAKwiXBVtQi0GwF/DZ8h+F599dWiaQi9tdde21555RXz96cW5f5uVVt33XVts802K8q005gAXr6Fmb/72o4++uiFnY7yP/744x/Pd/bkN998Yy+88ILtv//+nb21U9cffvjh8Y/CvHnzOnWfLhYBEeh6AhKAXT8GaoEItIzA5ptvHqIOcZeN/REjRlj//v1DNJTLEYzYlClTbLvttgvvIWJx1KhR9sUXX8Q5f0+nrb/++n/zNr322mvhQXz//ffjuu+//94mTJhgffv2tZVXXtn22GMPe/PNN+Ncoy877bSTnXXWWTWnvvzyy/BW+vttoxzhdcYZZ9h6661nK6ywgu24445W7hsXEXr1d4va8ssvb6uttprts88+9u2339qYMWPM3/Fr119/fbQTb+eHH34Y9VK+ww47GF6uddZZJ9pR9priXTv++OPtlFNOsTXXXNP22muvuK/+Cwzeffdd83d3F6d4Bs+6++67jXqWW2654Pv111/byJEjgyVt3WqrrWzq1KnFfez8/PPPduSRR9qKK64Y7br66qtrzueDmTNn2sCBA4PL7bffbquuumo+Fdv7778/2pALL7jgAhs0aJDdeuutEdal/mOPPdb+/PNPu+KKK+IfBMbtkksuybfEdo011rCdd975b+2suUgHIiACbUlAArAth0WNEoHmEUB0ZAHFU9inDJGUyxFWs2fPtiwAOb7oootCsCEePvjggxBQ3N+zZ0877LDD7I477uCwsDvvvNMQcZtssonxynFE0GeffWYPP/xwzBsbPHiwDR061PBWNTK8Swig8uvKp02bZmuttVa0lXvGjh0bAu+uu+6yt956yw455BAbNmyYvfPOO1HlG2+8Ec/Ycsstoz/PPfdceMUQNgg/2jd+/HhbsGBBfDbYYAP79NNPbb/99rPtt98++nvDDTfYpEmT7OKLL65p5uTJk61Xr17x/JtuuqnmXD545plnwoOK4K03Qqcnnnii4T1DlP7222+27bbb2owZM+ztt98OsXzEEUfYSy+9VNx6+umnxxjdd9999vjjj4fYbTQH78EHHwxRX9y4CDvvvfeePfLII/boo48Gd8QgY/bJJ5+EUL788svt3HPPtRdffLGmNoQyXmSZCIhAxQj4L1eZCIhANyLgc9KSe8uShx3TDz/8kFzEpM8//zy5iEruzQkS7gFL/qssuShoSObll1+O8z/++GOcd09Xcq9Wcu9WHLvASu6VSx0dHXH81FNPJRdByUVOTX3udUwunmrK8oF7GKNtLqJyUXLBllwExbF71uKZLtiK8+y4qExnn312lLlHLfncu5rz5QMXvemkk04qF6Vzzjknuac0uWezKKcf7hVL9AvjPveYFecXtkPd7umsOe3iOdhdd911NeWNDlyIplNPPTVOwXrZZZeNccrXutcw9e7du6YPMPZ5nskFcVx22223pVVWWSXfElsXkNGGXHj++ecn9zrG90Muc1GaNt5446LPlMPl0ksvzZfE1oV0XFdTqAMREIG2JyAPYMUEu5orAotLAK8eoUTm/OG5YY4f4T08gJRxjjAqGZ5477DXX389PEokM5BEgscQ++ijj2K7zTbb2BZbbFGEAgmhEiI+9NBD4zxeKuYeEjIkvJg/eBLxPDWyPn36RGg1exa5Fq8knkGM8Kr/ho325/rY8uxcZ/YANqp/YWV45PAMEqbNRgIH7ccblo2Q+L/Zr7/+GiHeRtfV349XkhDr1ltvXXDCy5cZ0yc8sbQt2+qrr26E9cs2a9asuJ8QcmfMxV6Mbb4HT+uAAQPCw1suy6H/XOYCtGbuaC7XVgREoL0J9Grv5ql1IiACS5rApptuGvPMCPcyFw7hhzG3r1+/fhHS5Bxz9DAE4d577x0f5gIizBAlhC0RJNkQZoR9mbfHlvPMj8OYJ8hcOoRlvdXPTyufp073ohmJFNRJKJe5bRh1LrPMMhFOZls2hCCGOOmsISrL4o/7KcPK5cw5/Dej/3PmzGl4Wf39zOe79tprzT2DMf+P8yzvkhnnNjSsrFS4KOFfxGa9kQleNvraqAzuZSOEz/eETAREoFoEelaruWqtCIjAkiCAFxAxxid786gXMfjYY4/FPK88/2/+/Pn21Vdf2WWXXRZLxeDpq/cCcS+JIYgdvH333ntv4anjHPP9mP/HnDkEaPmTRSLX1dsBBxwQc+OYl4YAHD16dHEJXkeEDG0p18c+YhbDm+bh5+Ke+h0PqUYd5XK8XmTQlgUXx3g+STbpjNFG+JXrWtj9eGNJxqGPiFy8r3kuI/fQLwRZeQ4eAr68TA/Peeihh2z48OE1j/HwcY2XLifm1Fz0Hw+Yr0g/ZSIgAtUiIAFYrfFSa0VgiRBA3JEQQYg0ewCpmP1bbrklRFcWgISCEUp44RAOeJhICKk3vIdkhB511FGxziBiJtuee+4ZoUsEHQKTTFNHPoYAAAL4SURBVFhEFUkF5SVp8vV5ixeMes4777xIlkBkZiN0jYeQrNjp06dHYgohbJIVSDTBfC5ghLWPO+64SBJBjJHUgaDFCHuSZEF7KMO7xbUff/yxnXDCCSHeHnjgAfM5cpHxS8JLZwyGeFDnzp37r7ch8J544ongQhj6mGOOCdGcb8SrCVsSQRC1CK8xnslcbhPim+cNGTIk3xZb+kXGMhnJPAMvI8YYLK4hXPEQy0RABKpFoHO/zarVN7VWBERgIQQQJsxPQ3Qw1ysbAhBvEUvCkBGLEd5jKZF77rkn5oThCbzqqqvyLTVbBBlLuxx44IE14VfCiYgyhMm4ceNi3h6Zwwiv8vNrKvv/Qa6ThaoRo2XzBIcQgJ4oEXPh8Hwh6HLbEYnMo6NNZKsyfw5BhycSYyFjwsd4/XJoGy8fbfVEl/DETZw4MYQXYrWzxpxHWOR5jP90PyIXTymhc7yyeDERzGW78sorgyH9RFTvuuuukTmcr6FvZO7m/uVyTwIJDyZLvbAm4TXXXBOLUyMyF8eYk8nyPgcffPDiVKN7RUAEuoBADw8Z/DW5pQserkeKgAiIwNJOgLA4Yg3vG2HkZhohb4RqTr7hWYh35hJ+9913S/zRLLtD+Nczp5d43apQBESguQTkAWwuX9UuAiLQzQmQjctiyng7m2kkixx00EG27777NvMxRd28C5i5iieffHJRph0REIHqEJAHsDpjpZaKgAiIQKcJNNMD2OnG6AYREIG2ISAB2DZDoYaIgAiIgAiIgAiIQGsIKATcGs56igiIgAiIgAiIgAi0DQEJwLYZCjVEBERABERABERABFpDQAKwNZz1FBEQAREQAREQARFoGwISgG0zFGqICIiACIiACIiACLSGgARgazjrKSIgAiIgAiIgAiLQNgQkANtmKNQQERABERABERABEWgNAQnA1nDWU0RABERABERABESgbQhIALbNUKghIiACIiACIiACItAaAhKAreGsp4iACIiACIiACIhA2xD4H9d0M1YHaMuRAAAAAElFTkSuQmCC\" width=\"640\">" + ], + "text/plain": [ + "<IPython.core.display.HTML object>" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "k_ = dispersion[\"k (rad/m)\"]\n", + "plt.figure()\n", + "for i in range(9):\n", + " plt.plot(k_*1e-6, dispersion[f\"f{i} (GHz)\"].values, ls=\"-\", marker=\"\", markersize=1, color=\"grey\", alpha=0.6)\n", + "plt.xlabel(\"Wave vector (rad/µm)\")\n", + "plt.ylabel(\"Frequency (GHz)\")\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/examples/round_tube_spatial_dep_anis.ipynb b/doc/examples/round_tube_spatial_dep_anis.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..88651a35f2776bcd1146d37bb2d6b275342f8646 --- /dev/null +++ b/doc/examples/round_tube_spatial_dep_anis.ipynb @@ -0,0 +1,180 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e115187a", + "metadata": {}, + "source": [ + "# Spatially dependent uniaxial anisotropy\n", + "\n", + "In this notebook we show how to set a spatially varying uniaxial anisotropy. To demonstrate we will use a nanotube (R=30 nm outer radius,r=20 nm inner radius) and set a radial uniaxial anisotropy." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "a0ea9478", + "metadata": {}, + "outputs": [], + "source": [ + "import tetrax as tx\n", + "\n", + "%matplotlib notebook\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "4220be52", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Setting geometry and calculating discretized differential operators on mesh.\n", + "Done.\n" + ] + } + ], + "source": [ + "sample = tx.create_sample(name=\"Nanotube_20nm_30nm\")\n", + "sample.Msat = 800e3\n", + "sample.Aex = 13e-12\n", + "mesh = tx.geometries.tube_cross_section(20,30,lc=3)\n", + "sample.set_geom(mesh)" + ] + }, + { + "cell_type": "markdown", + "id": "bd7bbdd8", + "metadata": {}, + "source": [ + "In the following we set a radial uniaxial anisotropy using the ```tx.vectorfields.radial(sample.xyz,1)``` function:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "53b30d39", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "db9116892fda4aaf93b32000a00b9b0c", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Output()" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "sample.Ku1 = 2e4 #J/m^3\n", + "sample.e_u = tx.vectorfields.radial(sample.xyz,1)\n", + "sample.plot(sample.e_u)" + ] + }, + { + "cell_type": "markdown", + "id": "fb1a08a1", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "c14f7e63", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Minimizing in using 'L-BFGS-B' (tolerance 1e-09) ...\n", + "Current energy length density: 6.316798137122507e-10 J/m mx = -0.01 my = -0.00 mz = 0.00\n", + "Relaxation with L-BFGS-B method was not succesful.\n", + "\n", + "Minimizing in using SLSQP method (tolerance 1e-09) ...\n", + "Current energy length density: 6.316832551337032e-10 J/m mx = -0.01 my = -0.00 mz = 0.000\n", + "Success!\n", + "\n" + ] + } + ], + "source": [ + "sample.mag = tx.vectorfields.radial(sample.xyz,1)\n", + "exp = tx.create_experimental_setup(sample)\n", + "exp.relax(tol=1e-9,continue_with_least_squares=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "72b573d5", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "356e14f2d7034176886ebc64a5a240eb", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Output()" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "sample.show()" + ] + }, + { + "cell_type": "markdown", + "id": "56a1a596", + "metadata": {}, + "source": [ + "" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/examples/tube_eq_state.png b/doc/examples/tube_eq_state.png new file mode 100644 index 0000000000000000000000000000000000000000..91b75db7fe51d188253234f7456c7d43a502f049 Binary files /dev/null and b/doc/examples/tube_eq_state.png differ diff --git a/doc/examples/tube_ext_field.png b/doc/examples/tube_ext_field.png new file mode 100644 index 0000000000000000000000000000000000000000..5c55fe1680efe270de3059166fbc49bff10f3f2d Binary files /dev/null and b/doc/examples/tube_ext_field.png differ diff --git a/doc/examples/tube_ini_state.png b/doc/examples/tube_ini_state.png new file mode 100644 index 0000000000000000000000000000000000000000..20cc636f451881f5c3834a404c7548e5c282796f Binary files /dev/null and b/doc/examples/tube_ini_state.png differ diff --git a/doc/examples/uanis_sp_dep.png b/doc/examples/uanis_sp_dep.png new file mode 100644 index 0000000000000000000000000000000000000000..2dce1bd10d1c2077bb3cee4cf1777d5b2885a6ea Binary files /dev/null and b/doc/examples/uanis_sp_dep.png differ diff --git a/doc/images/dispersion_tube.png b/doc/images/dispersion_tube.png new file mode 100644 index 0000000000000000000000000000000000000000..9f441c52322e8477d15d3dc17455d1fdf109e0d6 Binary files /dev/null and b/doc/images/dispersion_tube.png differ diff --git a/doc/images/exp_showBext.png b/doc/images/exp_showBext.png new file mode 100644 index 0000000000000000000000000000000000000000..fb5926e0eaa471bc219c9e9119f8186e901669a0 Binary files /dev/null and b/doc/images/exp_showBext.png differ diff --git a/doc/images/sample_rep.png b/doc/images/sample_rep.png new file mode 100644 index 0000000000000000000000000000000000000000..d6422f6dd9e96744c637d2e881274e14846c97c7 Binary files /dev/null and b/doc/images/sample_rep.png differ diff --git a/doc/images/sample_show.png b/doc/images/sample_show.png new file mode 100644 index 0000000000000000000000000000000000000000..ba3013797e8e13e71f0b0c3f48e58d43c88702e7 Binary files /dev/null and b/doc/images/sample_show.png differ diff --git a/doc/images/sample_showhelical.png b/doc/images/sample_showhelical.png new file mode 100644 index 0000000000000000000000000000000000000000..63b427c615925b36b41c3fc9258caa434fe966cf Binary files /dev/null and b/doc/images/sample_showhelical.png differ diff --git a/doc/images/sample_showmag.png b/doc/images/sample_showmag.png new file mode 100644 index 0000000000000000000000000000000000000000..bb1fc45a08c8b103a1cbaeb890272ea00e470c04 Binary files /dev/null and b/doc/images/sample_showmag.png differ diff --git a/doc/images/sample_vortex.png b/doc/images/sample_vortex.png new file mode 100644 index 0000000000000000000000000000000000000000..b16c7095d3d52ac0dfbbbb839e77f53c58830565 Binary files /dev/null and b/doc/images/sample_vortex.png differ diff --git a/doc/images/sample_with_mesh.png b/doc/images/sample_with_mesh.png new file mode 100644 index 0000000000000000000000000000000000000000..8c04112c55d0e1484cb6f41bfcf1867b79c9f4dd Binary files /dev/null and b/doc/images/sample_with_mesh.png differ diff --git a/doc/index.rst b/doc/index.rst index ea53b5a732d1ec7f4441112a91470f553db52385..fcb6ad9226844663b6defe979e3a99404ec05461 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -1,7 +1,4 @@ -.. TetraX documentation master file, created by - sphinx-quickstart on Sat Jan 15 20:14:29 2022. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. +.. TetraX documentation master file, created by sphinx-quickstart on Sat Jan 15 20:14:29 2022. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. .. image:: ../logo_large.png :width: 600 @@ -21,13 +18,13 @@ TetraX Documentation Examples <examples> Publications <publications> User Guide <usage> - Package documentation <modules> - + API Reference <modules> + Release Notes <release_notes> .. automodule:: tetrax -**Version**: 0.0.1 +**Version**: 1.1.0 TetraX is a package for finite-element-method (FEM) micromagnetic modeling with the aim to provide user friendly and versatile micromagnetic workflows. Apart from energy minimizers and an LLG solver, it aims to provide implementations of several FEM dynamic-matrix approaches to numerically calculate the normal modes and associated frequencies for magnetic specimen of different geometries such as confined samples, infinitely long waveguides, or infinitely extended multilayers. Apart from ferromagnets, the package will also be able to be used for antiferromagnetic samples in a future release. @@ -51,12 +48,24 @@ If you use TetraX for your research, please cite .. code-block:: TeX - @article{korberFiniteelementDynamicmatrixApproach2021a, - title = {TetraX: Finite-Element Micromagnetic-Modeling Package}, - TODO - } - - @article{korberFiniteelementDynamicmatrixApproach2021a, + @misc{TetraX, + author = {Körber, Lukas and + Quasebarth, Gwendolyn and + Hempel, Alexander and + Zahn, Friedrich and + Otto, Andreas and + Westphal, Elmar and + Hertel, Riccardo and + Kakay, Attila}, + title = {{TetraX: Finite-Element Micromagnetic-Modeling + Package}}, + month = jan, + year = 2022, + doi = {10.14278/rodare.1418}, + url = {https://doi.org/10.14278/rodare.1418} + } + + @article{korberFiniteelementDynamicmatrixApproach2021a, title = {Finite-element dynamic-matrix approach for spin-wave dispersions in magnonic waveguides with arbitrary cross section}, volume = {11}, @@ -69,10 +78,10 @@ If you use TetraX for your research, please cite } -License -------- +Source & license +---------------- -The source code of TetraX is licensed under the GNU GPL v3.0 Open-Source license. +The `source code <https://gitlab.hzdr.de/micromagnetic-modeling/tetrax>`_ of TetraX is licensed under the GNU GPL v3.0 Open-Source license. Indices and tables ================== diff --git a/doc/publications.rst b/doc/publications.rst index c7f0967306a6b3508ef5954a27b432fbec75c6b7..1c20af9f4ed66b7b08b65f3d7a653bfcbfb80152 100644 --- a/doc/publications.rst +++ b/doc/publications.rst @@ -7,9 +7,10 @@ Publications relevant for the this package: * Körber, *et al.*, "Finite-element dynamic-matrix approach for spin-wave dispersions in magnonic waveguides with arbitrary cross section", `AIP Advances 11, 095006 (2021) <https://doi.org/10.1063/5.0054169>`_ +* Körber and Kákay, "Numerical reverse engineering of general spin-wave dispersions: Bridge between numerics and analytics using a dynamic-matrix approach", `Phys. Rev. B 104, 174414 (2021) <https://doi.org/10.1103/PhysRevB.104.174414>`_ + Research articles who used this package or a previous version of it: * Körber, *et al.*, "Symmetry and curvature effects on spin waves in vortex-state hexagonal nanotubes", `Phys. Rev. B 104, 184429 (2021) <https://doi.org/10.1103/PhysRevB.104.184429>`_ -* Körber and Kákay, "Numerical reverse engineering of general spin-wave dispersions: Bridge between numerics and analytics using a dynamic-matrix approach", `Phys. Rev. B 104, 174414 (2021) <https://doi.org/10.1103/PhysRevB.104.174414>`_ diff --git a/doc/quickstart.rst b/doc/quickstart.rst index c01b9dd4acab105c673213172351bc84b0c07b4f..7eaa7cde613a5d9dc2a33fee76d12ee99f4f6056 100644 --- a/doc/quickstart.rst +++ b/doc/quickstart.rst @@ -13,7 +13,7 @@ Install this package using .. code-block:: bash - pip install git+https://gitlab.hzdr.de/kakay24/tetrax.git + pip install git+https://gitlab.hzdr.de/micromagnetic-modeling/tetrax.git To allow for 3D visualization in Jupyter notebooks, you additionally need to activate the k3d extension in your shell using @@ -46,7 +46,7 @@ When a ferromagnetric sample is created, it will be initalized with the material >>> sample -.. image:: _static/sample_rep.png +.. image:: images/sample_rep.png Any of these parameters can easily be changed using @@ -91,7 +91,7 @@ Once a mesh is set, we are able to inspect our mesh using >>> sample.show() -.. image:: _static/sample_show.png +.. image:: images/sample_show.png Note, that meshes for waveguide cross sections need to be embedded in the :math:`xy` plane, whereas meshes for layer samples need to be embedded on the :math:`y` axis. Per default, a sample does not have a magnetization vector field yet. We can set it using @@ -104,9 +104,9 @@ Per default, a sample does not have a magnetization vector field yet. We can set sample.mag = np.array([1, 0, 0]) sample.show() -.. image:: _static/sample_showmag.png +.. image:: images/sample_showmag.png -TetraX also provides a number of template vector fields in the :py:mod:`tetrax.vectorfields` submodule. For example, we can initialize our tube cross section in a helical state using +TetraX also provides a number of template vector fields in the :mod:`tetrax.vectorfields` submodule. For example, we can initialize our tube cross section in a helical state using .. code-block:: python @@ -116,7 +116,7 @@ TetraX also provides a number of template vector fields in the :py:mod:`tetrax.v sample.mag = tx.vectorfields.helical(sample.xyz, 60, 1) sample.show() -.. image:: _static/sample_showhelical.png +.. image:: images/sample_showhelical.png Run experiments @@ -140,9 +140,9 @@ The `name` parameter of the experimental setup is set to identify the data produ exp.Bext = B_phi * tx.vectorfields.helical(sample.xyz, 90, 1) exp.show() -.. image:: _static/exp_showBext.png +.. image:: images/exp_showBext.png -The experimental setup can also contain a microwave antenna, for example, to calculate a microwave-power absorption. For this, see **TODO example** or the User Guide. +The experimental setup can also contain a microwave antenna, for example, to calculate a microwave-power absorption. For this, see the example :doc:`examples/round_tube_absorption` or the User Guide. Calculate equilibrium state ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -158,14 +158,14 @@ Once the experimental setup is set up, we can calculate the equilibrium-magnetiz >>> sample.show() -.. image:: _static/sample_vortex.png +.. image:: images/sample_vortex.png We see that the equilibrium state of the sample in this setup is indeed a vortex state. Normal modes analysis ^^^^^^^^^^^^^^^^^^^^^ -We can calculate the dispersion of the (here) propagating normal modes using our propagating wave eigensolver. Depending on the geometry and magnetic order, the corresponding eigensolver will automatically be selected. It returns the mode frequencies (here: dispersion) as a :py:ojb:`pandas:DataFrame` and saves the spatial (here: lateral) mode profiles as `vtk` files to the `mode-profiles/` directory in the directory of the experimental setup. +We can calculate the dispersion of the (here) propagating normal modes using our propagating wave eigensolver. Depending on the geometry and magnetic order, the corresponding eigensolver will automatically be selected. It returns the mode frequencies (here: dispersion) as a :py:obj:`pandas:DataFrame` and saves the spatial (here: lateral) mode profiles as `vtk` files to the `mode-profiles/` directory in the directory of the experimental setup. .. code-block:: python @@ -188,7 +188,7 @@ We can plot the obtained dispersion easily using matplotlib. plt.ylabel("f (GHz)") plt.show() -.. image:: _static/dispersion_tube.png +.. image:: images/dispersion_tube.png As seen, we have recovered the asymmetric spin-wave dispersion in a vortex-state magnetic nanotube. [1]_ The mode profiles can be visualized, for example, using ParaView. A built-in method to visualize mode profiles will be implemented in the future. diff --git a/doc/release_notes.rst b/doc/release_notes.rst new file mode 100644 index 0000000000000000000000000000000000000000..f388e16dbc373388ec8fa93a456d3be65f8f104c --- /dev/null +++ b/doc/release_notes.rst @@ -0,0 +1,47 @@ +Release Notes +============= + +Version 1.1.0 +------------- +`2022-03-31` + +New features +^^^^^^^^^^^^ +- Added the :func:`tetrax.sample_average() <tetrax.helpers.math.sample_average>` function available, which takes the average of a vector or scalar field in a given sample (can be volume, surface or line) +- ``verbose={True, False}`` added to all numerical experiments, allows to silence all output (except warnings) +- Saving vector/scalar fields to vtk files is now possible using :func:`tetrax.write_field_to_file() <tetrax.helpers.io.write_field_to_file>` by + + .. code-block:: python + + tetrax.write_field_to_file(field, sample) + + or with the optional keywords + + .. code-block:: python + + tetrax.write_field_to_file(field, sample, fname, qname) + + As an alternative, the method :func:`AbstractSample.field_to_file() <tetrax.core.sample.AbstractSample.field_to_file>` can be used, where the sample parameters is obviously omitted. +- Added new equilibrium states :func:`bloch_wall <tetrax.vectorfields.bloch_wall>` and :func:`neel_wall <tetrax.vectorfields.neel_wall>` to :mod:`tetrax.vectorfields`. +- Added new geometry :func:`tube_segment_cross_section <tetrax.geometries.geometries2D.tube_segment_cross_section>` for waveguide samples to :mod:`tetrax.geometries`. +- Plotting of scalar and vector fields on a sample is now possible using the :func:`plot() <tetrax.core.sample.AbstractSample.plot>` method of a sample object. +- Pertubation analysis and reverse-engineering of general spin-wave dispersions according to `Phys. Rev. B 104, 174414 (2021) <https://doi.org/10.1103/PhysRevB.104.174414>`_ is now possible within the :func:`eigenmodes() <tetrax.core.experimental_setup.ExperimentalSetup.eigenmodes>` experiment, see User Guide :doc:`Numerical Experiments </usage/experiments>`. +- Implemented cubic anistropy (linearized dynamic field not properly tested yet). + +Minor changes +^^^^^^^^^^^^^ +- :func:`relax() <tetrax.core.experimental_setup.ExperimentalSetup.relax>` experiment now returns only a boolean denoting the relaxation success +- magnetization is automatically saved into folder of experimental setup after running :func:`relax() <tetrax.core.experimental_setup.ExperimentalSetup.relax>` +- dispersion is automatically saved to into folder of experimental setup after running :func:`eigenmodes() <tetrax.core.experimental_setup.ExperimentalSetup.eigenmodes>` + +Other +^^^^^ +- Populated User Guide and API reference. +- Added more examples. +- Indroduced :class:`MeshVector <tetrax.core.mesh.MeshVector>`, :class:`MeshScalar <tetrax.core.mesh.MeshScalar>` and related data types. + +Version 1.0.1 +------------- +`2022-03-07` + +Initial release. diff --git a/doc/requirements.txt b/doc/requirements.txt index 3bc94fa4255be9d8e3a6403b2e3edd103d652ca3..4dda9afea02faa661c8c0cdca50e8d4d0375dff6 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -2,6 +2,7 @@ sphinx==4.2.0 pydata-sphinx-theme numpydoc nbsphinx +sphinx_copybutton sphinx-gallery Cython k3d diff --git a/doc/tetrax.core.rst b/doc/tetrax.core.rst index 872d01524cb88f123e973c41c04e65c7fe1dbd04..4bca3f11b137cd136a7b27d0a5a1bde2a71da342 100644 --- a/doc/tetrax.core.rst +++ b/doc/tetrax.core.rst @@ -20,6 +20,14 @@ tetrax.core.experimental\_setup module :undoc-members: :show-inheritance: +tetrax.core.mesh module +----------------------- + +.. automodule:: tetrax.core.mesh + :members: + :undoc-members: + :show-inheritance: + tetrax.core.operators module ---------------------------- diff --git a/doc/tetrax.rst b/doc/tetrax.rst index f18f35b07f06e7008370f3ca5e2b8388594dfc45..51d462f7e4c4bd52323e1d646d015011b547294e 100644 --- a/doc/tetrax.rst +++ b/doc/tetrax.rst @@ -9,19 +9,12 @@ Subpackages tetrax.core tetrax.experiments + tetrax.geometries tetrax.helpers Submodules ---------- -tetrax.geometries module ------------------------- - -.. automodule:: tetrax.geometries - :members: - :undoc-members: - :show-inheritance: - tetrax.vectorfields module -------------------------- diff --git a/doc/usage.rst b/doc/usage.rst index 18e9ae06f3fed00c64c99a0c7e203bba91470913..c0ec2203923e148b980f03529bd84956c664717c 100644 --- a/doc/usage.rst +++ b/doc/usage.rst @@ -1,4 +1,13 @@ User Guide ========== -tba +.. toctree:: + :caption: User Guide + :numbered: + + usage/introduction + usage/installation + usage/sample + usage/experiments + usage/visualization + usage/appendix diff --git a/doc/usage/appendix.rst b/doc/usage/appendix.rst new file mode 100644 index 0000000000000000000000000000000000000000..90152b1ec01ab752affc38856bcbf80c8e854100 --- /dev/null +++ b/doc/usage/appendix.rst @@ -0,0 +1,12 @@ +Appendix +======== + +.. _modeling_section: +Micromagnetic modeling and implementation +----------------------------------------- + + + + +Data structures +--------------- diff --git a/doc/usage/exp_antenna_cpw.png b/doc/usage/exp_antenna_cpw.png new file mode 100644 index 0000000000000000000000000000000000000000..de9899daeff898f77f74e21cc9b302792f40d68d Binary files /dev/null and b/doc/usage/exp_antenna_cpw.png differ diff --git a/doc/usage/exp_antenna_loop.png b/doc/usage/exp_antenna_loop.png new file mode 100644 index 0000000000000000000000000000000000000000..dd65e3cce5b68af32bf0b6d275fdfc689cf12059 Binary files /dev/null and b/doc/usage/exp_antenna_loop.png differ diff --git a/doc/usage/exp_antenna_stripline.png b/doc/usage/exp_antenna_stripline.png new file mode 100644 index 0000000000000000000000000000000000000000..4145429ded8543cf6666387fd654afb18377b98b Binary files /dev/null and b/doc/usage/exp_antenna_stripline.png differ diff --git a/doc/usage/experiments.rst b/doc/usage/experiments.rst new file mode 100644 index 0000000000000000000000000000000000000000..82f6f78ec66e2fc4f88abf6fd395917a5bf210c6 --- /dev/null +++ b/doc/usage/experiments.rst @@ -0,0 +1,250 @@ +Numerical Experiments +===================== + +.. contents:: Table of Contents + :depth: 2 + :local: + :backlinks: none + +The ``ExperimentalSetup`` object +-------------------------------- + +Creating a setup and conducting experiments +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The central object to contuct numerical experiments is the :mod:`ExperimentalSetup <tetrax.core.experimental_setup.ExperimentalSetup>` class which is always associated to a sample. It can be created using + +.. code-block:: python + >>> exp = tetrax.create_experimental_setup(sample, name="my_experiment") + +As with the names of sample objects, the ``name`` attribute of the setup is only for saving purposes and is used to create directories for data calculated in numerical experiments. Within the setup, experimental conditions can be specified: + +- ``Bext``, a static external magnetic field given in units of Tesla, which, like the magnetization of the sample, is a :class:`MeshVector <tetrax.core.mesh.MeshVector>` object (an array of shape ``(nx, 3)``) for which template vectorfields are found in the :mod:`tetrax.vectorfields` module. Homogenous external fields can also be set for examples as + + .. code-block:: python + + exp.Bext = [0, 0, 5e-3] + +- ``antenna`` is the microwave antenna in the experimental setup for which the microwave absorption of the spin-wave modes in the sample can be calculated, or which (in ``confined`` samples) can be used to apply time-dependent external fields. An antenna can be set using + + .. code-block:: python + + exp.antenna = tetrax.core.experimental_setup.CPWAntenna(150, 30, 54) + + +The full experimental setup, including external field and microwave antenna, can be visualized using + +.. code-block:: python + + exp.show() + +Within the setup, a number of numerical experiments can be conducted (which are documented below) as + +.. code-block:: python + + exp.relax() + exp.eigenmodes() + exp.absorption() + exp.evolve() # only for confined samples + +Microwave antennae +^^^^^^^^^^^^^^^^^^ +The available microwave antennae, called from the :mod:`tetrax.core.experimental_setup` module, are + +.. autoclass:: tetrax.core.experimental_setup.CPWAntenna + :undoc-members: + +.. autoclass:: tetrax.core.experimental_setup.CurrentLoopAntenna + :undoc-members: + +.. autoclass:: tetrax.core.experimental_setup.HomogeneousAntenna + :undoc-members: + +.. autoclass:: tetrax.core.experimental_setup.StriplineAntenna + :undoc-members: + +Energy minimization +------------------- + +For many purposes it is important to calculate the magnetic equilibrium state of a sample under given external conditions. There are many different ways to do this, with the simplest one being minimizing the total magnetic energy (the energy length density for waveguide samples and the energy area density for layer samples). + +.. warning:: + The relaxation in waveguide samples is not always stable and should be conducted with care. + +After initializing the magnetization of the sample (``mag``) can be done using the :func:`relax() <tetrax.core.experimental_setup.ExperimentalSetup.relax>` method. + +.. autofunction:: tetrax.core.experimental_setup.ExperimentalSetup.relax + +Linear magnetization dynamics +----------------------------- + +Spin-wave normal-mode analysis +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Mode frequencies (dispersion) and spatial profiles +"""""""""""""""""""""""""""""""""""""""""""""""""" + +To obtain the frequencies and spatial mode profiles of the spin-wave normal modes in a given sample, the linearized +lossless Landau-Lifshitz-Gilbert equation + +.. math:: + :label: ev_problem + + \frac{\omega_\nu}{\omega_M} \mathbf{m}_\nu = i [\mathbf{m}_0 \times \hat{\mathbf{\Omega}}]\mathbf{m}_\nu \quad \text{with} \quad \mathbf{m}_0 \perp \mathbf{m}_\nu + +can be solved numerically using a dynamic-matrix approach. Here, :math:`\mathbf{m}_\nu` are the complex-valued spatial profiles of the normal modes, :math:`\mathbf{m}_0` is the normalized equilibrium magnetization, :math:`\omega_M = \mu_0\gamma M_\mathrm{s}` is the characteristic frequency of the material and the operator :math:`\hat{\mathbf{\Omega}}` is given as + +.. math:: + + \hat{\mathbf{\Omega}} = h_0 \hat{\mathbf{\Omega}} + \hat{\mathbf{N}} + +with the projection of the (unitless) equilibrium effective field onto the equilibrium magnetization + +.. math:: + + h_0 = \mathbf{m}_0 \cdot (\mathbf{h}_\mathrm{ext} + \hat{\mathbf{N}}\mathbf{m}_0 ). + +The operator :math:`\hat{\mathbf{N}}` is a certain Hermetian operator, which contains the mangetic self interactions + +.. math:: + + \hat{\mathbf{N}} = \hat{\mathbf{N}}^\mathrm{(exc)} + \hat{\mathbf{N}}^\mathrm{(dip)} + \hat{\mathbf{N}}^\mathrm{(uni)} + \hat{\mathbf{N}}^\mathrm{(cub)} + \hat{\mathbf{N}}^\mathrm{(iDMI)} + \hat{\mathbf{N}}^\mathrm{(bDMI)} + +and which we refer to here as the magnetic tensor. Its application to the magnetization yields the effective field. For the cubic anistropy, a linearized operator is used, except for the equilibrium field :math:`h_0` for which the full nonlinear field is used. For details, about the individual terms and their implementation, refer to the :doc:`Micromagnetic model and implementation </usage/appendix>` section in the appendix. + +To implement the constraint :math:`\mathbf{m}_0 \perp \mathbf{m}_\nu`, the eigenvalue problem :eq:`ev_problem` is projected into the subspace locally orthogonal to the equilibrium direction :math:`\mathbf{m}_0`. + +For propagating spin waves in waveguide, or layer samples, the eigenvalue problem is projected into a single cross section of the sample by making the replacements + +.. math:: + + \mathbf{m}_\nu \rightarrow \mathbf{m}_{\nu k} = \mathbf{m}_{\nu} e^{-ikz} \\ + \hat{\mathbf{N}} \rightarrow \hat{\mathbf{N}}_k = e^{-ikz}\hat{\mathbf{N}} e^{ikz} + +with the wave vector in :math:`z` direction :math:`k`, the lateral mode profiles :math:`\mathbf{m}_{\nu k}` and the propagating-wave tensors :math:`\hat{\mathbf{N}}_k`. The eigenvalue problem is then solved for the lateral profiles and the dispersion :math:`\omega_\nu(k)`. For of the propagating-wave dynamic-matrix approach used here, see Ref [1]_. + +.. note:: + During the course of this User Guide, volumentric :math:`\mathbf{m}_\nu` and lateral profiles :math:`\mathbf{m}_{\nu k}` are often interchangeably denoted simply as "mode profiles", unless stated otherwise. + + +In `TetraX`, the eigensystem of a given ``sample`` in an experimental setup ``exp`` can be calculated by + +.. code-block:: python + + >>> dispersion = exp.eigenmodes(...) + +or simply + +.. code-block:: python + + >>> exp.eigenmodes(...) + +By default, the oscillation frequencies and the mode profiles are saved into the directory of the experimental setup. + +.. autofunction:: tetrax.core.experimental_setup.ExperimentalSetup.eigenmodes + +Linear mode damping +""""""""""""""""""" + +The linewidths :math:`\Gamma_\nu` of the calculated normal modes can be calculated from the mode profiles and frequencies according to + +.. math:: + + \Gamma_\nu = \alpha_\mathrm{G} \epsilon_\nu \omega_\nu + +with :math:`\alpha_\mathrm{G}` being the Gilbert-damping parameter and :math:`\epsilon_\nu` being the ellipticity coefficient of the respective mode which is calculated from the mode profile :math:`\mathbf{m}_\nu` according to + +.. math:: + + \epsilon_\nu = -i\frac{\langle\vert\mathbf{m}_\nu\vert^2\rangle_\mathrm{S}}{\langle \mathbf{m}_\nu^* \cdot \mathbf{m}_0\times\mathbf{m}_\nu\rangle_\mathrm{S}} + +with the equilibrium-magnetization direction :math:`\mathbf{m}_0` and the sample average :math:`\langle ... \rangle_\mathrm{S}` (see Ref [2]_ for spin waves in general samples and Ref [3]_ for propagating waves in waveguide samples). + +.. warning:: + For the moment, the linewidths are only calculated within the :func:`absorption() <tetrax.core.experimental_setup.ExperimentalSetup.absorption>` experiment. A possibility to obtain the linewidths seperately will be added in version ``1.2.0``. + +Microwave absorption and dynamic susceptibility +""""""""""""""""""""""""""""""""""""""""""""""" + +If the experimental setup has an ``antenna``, the high-frequency power absorption (microwave absorption) of the spin-wave normal modes can be calculated with respect to this antenna, as a function of frequency and wave vector +(only for propagating waves). + +The wave-vector- and frequency-dependent microwave absorption :math:`P(k,\omega)` is calculated according to + +.. math:: + + P(k,\omega) \propto \sum\limits_\nu \frac{\vert h_\nu(k)\vert^2}{[\omega_\nu(k) - \omega]^2-\Gamma_\nu^2(k)} + +where the index :math:`\nu` runs over all calculated modes (per wave vector), :math:`\omega_\nu(k)` are the mode +angular frequencies and :math:`\Gamma_\nu(k) = \alpha_\mathrm{G}\epsilon_\nu(k)\omega_\nu(k)` are the linewiths, +determined from the Gilbert damping parameter :math:`\alpha_\mathrm{G}` and the mode ellipticites (see previous section). The factor :math:`h_\nu(k)` is given by the overlap of the microwave +field distribution (possible wave-vector dependent) +with the respective mode profile + +.. math:: + h_\nu(k) = -i\frac{\langle\mathbf{m}_\nu^* \cdot \mathbf{h}(k)\rangle_\mathrm{S}}{\langle \mathbf{m}_\nu^* \cdot \mathbf{m}_0\times\mathbf{m}_\nu\rangle_\mathrm{S}} + + +with the equilibrium-magnetization direction :math:`\mathbf{m}_0` and the sample average :math:`\langle ... \rangle_\mathrm{S}` (see, for example, Appendix A of Ref [3]_ for details). + +.. note:: + Naturally, for confined samples, the :math:`k` dependence of an antenna is exchanged by a :math:`z` dependence. + +In `TetraX`, the microwave absorption of a given ``sample`` in an experimental setup ``exp`` can be calculated by + +.. code-block:: python + + >>> (absorbed_power, wave_vectors, frequencies) = exp.absorption(...) + +or, for a single wave vector + +.. code-block:: python + + >>> subsceptibility = exp.absorption(k=0, ...) + +.. autofunction:: tetrax.core.experimental_setup.ExperimentalSetup.absorption + +Perturbation analysis and reverse-engineering of analytical spin-wave dispersions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Within the :func:`eigenmodes() <tetrax.core.experimental_setup.ExperimentalSetup.eigenmodes>`, `TetraX` also provides the possibility to perform disentagle dipole-dipole hybridized modes and to numerically reverse-engineer the stiffness fields within a spin-wave dispersion (see Ref. [4]_). + +For example, the zeroth-order dipolar perbutation of exchange modes can be calculated using + +.. code-block:: python + + >>> exp.absorption(..., no_dip=True, perturbed_dispersion=True) + +This will first calculate the normal modes without dipolar interaction and then calculate their perturbed frequency including dynamic dipolar fields and neglecting hybridization. As stated in [4]_ only works well, if the spatial mode profiles are not changed considerably by the dipolar interaction. If desired, dipolar fields can still be included in the equilibrium field by supplying ``h0_dip=True``. + +During the calculationg, the stiffness fields :math:`N_\nu^{(ij)}` (:math:`i,j=1,2`) within the general spin-wave dispersion + +.. math:: + \frac{\omega_\nu(k)}{\omega_M} = \mathrm{Im}\, N_\nu^{(21)} + \sqrt{\Big(N_\nu^{(11)} + \langle h_0 \rangle_\mathrm{S} \Big)\Big(N_\nu^{(22)} + \langle h_0 \rangle_\mathrm{S} \Big) - \Big(\mathrm{Re}\, N_\nu^{(21)} \Big)^2} + +are calculated for each mode and magnetic interaction [4]_. + +They can be saved into the dispersion dataframe by supplying ``save_stiffness_fields=True``. This method can of course also be used for the true dipole-exchange normal modes (``no_dip=False``) and allow, for example, to disentangle the contributions of different magnetic interactions to the magnetochiral stiffness field responsible for any dispersion asymmetry, + +.. math:: + + \mathrm{Im}\, N_\nu^{(21)} \equiv \mathcal{A}_\nu = \mathcal{A}_\nu^\mathrm{(dip)} + \mathcal{A}_\nu^\mathrm{(DMI)} + ... + +For details of the calculations, we refer to Ref [4]_. + +Nonlinear LLG dynamics +---------------------- + +This feature will be implemented in a future release. + + +References +---------- + +.. [1] Körber, *et al.*, "Finite-element dynamic-matrix approach for spin-wave dispersions in magnonic waveguides with arbitrary cross section", `AIP Advances 11, 095006 (2021) <https://doi.org/10.1063/5.0054169>`_ + +.. [2] Verba, *et al.*, "Damping of linear spin-wave modes in magnetic nanostructures: Local, nonlocal, and coordinate-dependent damping", `Phys. Rev. B 98, 104408 (2018) <https://doi.org/10.1103/PhysRevB.98.104408>`_ + +.. [3] Körber, *et al.*, "Symmetry and curvature effects on spin waves in vortex-state hexagonal nanotubes", `Phys. Rev. B 104, 184429 (2021) <https://doi.org/10.1103/PhysRevB.104.184429>`_ + +.. [4] Körber and Kákay, "Numerical reverse engineering of general spin-wave dispersions: Bridge between numerics and analytics using a dynamic-matrix approach", `Phys. Rev. B 104, 174414 (2021) <https://doi.org/10.1103/PhysRevB.104.174414>`_ \ No newline at end of file diff --git a/doc/usage/installation.rst b/doc/usage/installation.rst new file mode 100644 index 0000000000000000000000000000000000000000..40243bbc863d25149ee0ceb5a669446ff38617ab --- /dev/null +++ b/doc/usage/installation.rst @@ -0,0 +1,33 @@ +Installation +============ + +The installation of the `TetraX` package requires python and a C compiler. For MacOs users we recommend the installation of the latest `Anaconda <https://www.anaconda.com/products/distribution>`_ package. + +To install this package on a computer with administrator rights use + +.. code-block:: bash + + pip install git+https://gitlab.hzdr.de/micromagnetic-modeling/tetrax.git + +On a cluster with no administrator rights you need to add the `--user` option: + +.. code-block:: bash + + pip install git+https://gitlab.hzdr.de/micromagnetic-modeling/tetrax.git --user + +During the installation the pip package manager will check the dependencies and install all python related packages the `TetraX` uses. + +NOTE: To avoid updating your packages, please create a `python virtual environment <https://docs.python.org/3/tutorial/venv.html>`_, activate it and install both the Jupyter notebook as well as the `TetraX` in this virtual environment. After your finite element simulations are done you can `deactivate <https://docs.python.org/3/tutorial/venv.html>`_ your virtual environment. + +To allow for 3D visualization in Jupyter notebooks, you additionally need to activate the k3d extension in your shell using + +.. code-block:: bash + + $ jupyter nbextension install --py --sys-prefix k3d + $ jupyter nbextension enable --py --sys-prefix k3d + +Now you are ready to use TetraX in your python scripts or Jupyter notebook. + +.. code-block:: python + + import tetrax as tx diff --git a/doc/usage/introduction.rst b/doc/usage/introduction.rst new file mode 100644 index 0000000000000000000000000000000000000000..63865059c444a9455df49a171944b3f3f7a28122 --- /dev/null +++ b/doc/usage/introduction.rst @@ -0,0 +1,70 @@ +Introduction +============ + +.. contents:: Table of Contents + :depth: 2 + :local: + :backlinks: none + +Basic usage +----------- + +`TetraX` is a package for finite-element-method (FEM) micromagnetic modeling with the aim to provide user friendly and versatile micromagnetic workflows. Usually, for FEM simulations one requires a software to create the finite element mesh which is used for doing the actual simulations and a visualization program to analyse the results. With the possibility to use `TetraX` from Jupter notebooks we tried to combine the mesh creation, simulation and visualization into one package. Furthermore, by providing pre-defined functions to create finite element meshes of different geometries scientist do not need to become experts of finite element mesh creation but can right away perform simulations and concentrate on the science. By running the simulations in Jupyter notebooks, one can use all the benefits of Python, such as running multiple simulations over large parameter spaces, easy visualization of dataframes or vectorfields defined on the finite element meshes. Moreover, the workflows of simulations can be shared between colleagues or published as supplementary materials of manscuripts. To get the taste, please check the :doc:`/quickstart`. + +Those who prefer to run simulations remotely or have access to high-performance clusters, do not need to use Jupyter notebooks. However, we strongly recommend to create a Juptyer notebook and export it as a python code that can be run on clusters using a job scheduling system as the `SLURM <https://www.schedmd.com/index.php>`_. + + + +Using TetraX in your research +----------------------------- + +If you use `TetraX` for your research, we kindly ask you please cite + +.. [1] L. Körber, G. Quasebarth, A. Hempel, F. Zahn, A. Otto, E. Westphal, R. Hertel and A. Kákay (2022). + "TetraX: Finite-Element Micromagnetic-Modeling Package", + Rodare. DOI: `10.14278/rodare.1418 <https://doi.org/10.14278/rodare.1418>`_ +.. [2] L. Körber, G. Quasebarth, A. Otto and A. Kákay, "Finite-element dynamic-matrix approach for + spin-wave dispersions in magnonic waveguides with arbitrary + cross section", `AIP Advances 11, 095006 (2021) <https://doi.org/10.1063/5.0054169>`_ + + +.. code-block:: TeX + + @misc{TetraX, + author = {Körber, Lukas and + Quasebarth, Gwendolyn and + Hempel, Alexander and + Zahn, Friedrich and + Otto, Andreas and + Westphal, Elmar and + Hertel, Riccardo and + Kakay, Attila}, + title = {{TetraX: Finite-Element Micromagnetic-Modeling + Package}}, + month = jan, + year = 2022, + doi = {10.14278/rodare.1418}, + url = {https://doi.org/10.14278/rodare.1418} + } + + @article{korberFiniteelementDynamicmatrixApproach2021a, + title = {Finite-element dynamic-matrix approach for spin-wave dispersions + in magnonic waveguides with arbitrary cross section}, + volume = {11}, + doi = {10.1063/5.0054169}, + language = {en}, + journal = {AIP Advances}, + author = {Körber, L and Quasebarth, G and Otto, A and Kákay, A}, + year = {2021}, + pages = {095006}, + } + +Problems or feature requests +---------------------------- + +`TetraX` is maintained by the `Micromagnetic Modeling Group <https://www.hzdr.de/db/Cms?pOid=55944&pNid=107>`_ of Dr. Attila Kákay at the `Helmholtz-Zentrum Dresden - Rossendorf <https://www.hzdr.de>`_. In case you experience any problem, being related with the `TetraX` package or installation issues, please directly write to our `support email <mailto:gitlab-incoming+micromagnetic-modeling-tetrax-4372-issue-@hzdr.de>`_ or contact either `Lukas Körber <mailto:l.koerber@hzdr.de>`_ or `Attila Kákay <mailto:a.kakay@ghzdr.de>`_. We are happy to help you with implementing new features for your studies. Please approach us to discuss the details and find the optimal way. + +Acknowledgements +---------------- + +The authors are very thankful to Steffen Boerm, Burkhard Clauß, Pedro Landeros, Jorge A. Otálora, Jürgen Lindner and Jürgen Fassbender for fruitful discussions. We are grateful to Henrik Schulz and Jens Lasch for their continuous support of our computational infrastructure. Financial support from the Deutsche Forschungsgemeinschaft within the programs under Grant Nos. KA 5069/1-1 and KA 5069/3-1 is gratefully acknowledged. \ No newline at end of file diff --git a/doc/usage/polygonal_cross_section.png b/doc/usage/polygonal_cross_section.png new file mode 100644 index 0000000000000000000000000000000000000000..20af5e7833ed48a5768bae7d9273ffc1d9c34fcb Binary files /dev/null and b/doc/usage/polygonal_cross_section.png differ diff --git a/doc/usage/rectangle_cross_section.png b/doc/usage/rectangle_cross_section.png new file mode 100644 index 0000000000000000000000000000000000000000..4ef97ee9d2c9a97f2631c945665d44e6ab749213 Binary files /dev/null and b/doc/usage/rectangle_cross_section.png differ diff --git a/doc/usage/round_wire_cross_section.png b/doc/usage/round_wire_cross_section.png new file mode 100644 index 0000000000000000000000000000000000000000..ca5b1bc442c6b87378ba9a8f6b755c24e1ace158 Binary files /dev/null and b/doc/usage/round_wire_cross_section.png differ diff --git a/doc/usage/sample.rst b/doc/usage/sample.rst new file mode 100644 index 0000000000000000000000000000000000000000..551debbee810a8f976ff0c6d0e24d9df039b3294 --- /dev/null +++ b/doc/usage/sample.rst @@ -0,0 +1,276 @@ +Creating and defining a sample +============================== + +.. contents:: Table of Contents + :depth: 2 + :local: + :backlinks: none + +The sample object +----------------- + +Initialization +^^^^^^^^^^^^^^ + +The central object in a `TetraX` workflow is the sample, represented by an instance of the +:class:`AbstractSample <tetrax.core.sample.AbstractSample>` class. Much like physical samples, they are +characterized by a geometry, a set of material parameters, a magnetization state and the involved magnetic +interactions. In numerical experiments, +the sample is additionally characterized by an underlying mesh, a scale, a set of differential operators +(gradient, divergence, etc.) tied to the mesh and a number of other attributes neccessary for computation. We +can create a sample using + +.. code-block:: python + + >>> sample = tx.create_sample("name of my sample") + +This will initialize a sample (without a geometry yet) with a set of default material parameters. +These parameters can be accessed and set in the usual pythonic way + +.. code-block:: python + + >>> sample.Msat = 800e3 + >>> print(sample.Msat) + 800.0e3 + + >>> print(sample.name) + "name of my sample" + +The name of the sample is set purely for the purpose of storing data in a meaningful way, as all data obtained in +numerical experiments is saved in a directory named after the sample. + +Setting a geometry +^^^^^^^^^^^^^^^^^^ + +Before proceeding with anything +further, a specific geometry for the sample needs to be set. This is done by +supplying a mesh (in `meshio` format) to the sample using + +.. code-block:: python + + >>> sample.set_geom(mesh) + +In the :mod:`tetrax.geometries` submodule, we supply a couple of templates to generate common meshes, such as +cuboids, prisms, tubes, wires, and so forth. However, desired meshes can be generated also using `pygmsh <https://pygmsh.readthedocs.io/en/latest/>`_ +or even any external software, as long as the generated mesh files are readable by `meshio <https://github.com/nschloe/meshio>`_. Mesh files can be read in +using + +.. code-block:: python + + >>> sample.read_mesh(filename) + +When setting a mesh/geometry, under the hood, certain preprocessing necessary for later computations is performed. For example, +the differential operators on the mesh or the normal vector field of the sample are calculated. After these calculations +have been performed, important numerical parameters of the mesh can be accessed, including + +- ``mesh``, the mesh itself in `meshio <https://github.com/nschloe/meshio>`_ format, +- ``nx``, the total number of nodes in the mesh, +- ``nb``, the number of boundary nodes in the mesh, and +- ``xyz``, the coordinates at each node as a :class:`MeshVector <tetrax.core.mesh.MeshVector>`. These coordinates are expressed in units of +- ``scale``, the characteristic length of the sample. The default is ``1e-9`` which means that the coordinates on the mesh are given in nanometers. + +.. note:: + The mesh of the sample can visualized using the :meth:`show <tetrax.core.sample.AbstractSample.show>` method on the sample, to allow for checking + if the mesh was created/obtained according to the request or mesh in mind: + +.. code-block:: python + + >>> sample.show() + + +Magnetization vector field +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Once a mesh has been assigned to the sample, the magnetization vector field of the sample (``mag``) can be set for example +as + +.. code-block:: python + + >>> sample.mag = [0, 0, 1] + +.. note:: + A magnetization vector field can only be set for samples which already have a mesh. + +In antiferromagnetic cases, the sample has a magnetization vector field for each sublattice, ``mag1`` and ``mag2``. +To initialize an inhomogeneous magnetization, an array of shape ``(nx, 3)`` needs to supplied which can be +seen as a list of triplets with each triplet representing the vector at +a specific node. For convenience, a couple of template vector fields (such as domain walls, vortices, etc.) +can be found in the :mod:`tetrax.vectorfields` submodule. These fields can be called by supplying ``xyz``, the +coordinates on the mesh. + +.. code-block:: python + + >>> sample.mag = tx.vectorfields.bloch_wall(sample.xyz, ...) + +When setting a magnetization field, the input field is automatically normalized and internally converted +to a :class:`MeshVector <tetrax.core.mesh.MeshVector>`, which is a data structure that allows to easily access the different components +of the magnetization. + +.. code-block:: python + + >>> sample.mag.x + MeshScalar([0.0, 0.0, 0.12, 0.15, ...]) + +Here, we see that the individual components of a mesh vector are :class:`MeshScalars <tetrax.core.mesh.MeshScalar>`, +another basic data structure in `TetraX`. We can easily calculate the averages of these objects using the +:func:`sample_average <tetrax.helpers.math.sample_average>` function from the :mod:`tetrax.helpers.math` module, which +takes some vector- or scalar field and the sample itself as an input. + +.. code-block:: python + + >>> m = tx.sample_average(sample.mag, sample) + >>> m + [0.98, 0.02, 0.0] + + >>> mx = tx.sample_average(sample.mag.x, sample) + >>> mx + 0.98 + +.. note:: + The reason, why :func:`sample_average <tetrax.helpers.math.sample_average>` is not called ``volume_average`` + is because not all samples have to be three-dimensional. For example, in waveguide samples, :func:`sample_average <tetrax.helpers.math.sample_average>` + calculates the average in a cross section. + +The mesh and the current magnetization of a sample (if available) can always be visualized using the +:meth:`show <tetrax.core.sample.AbstractSample.show>` method. + +.. code-block:: python + + >>> sample.show() + +For further details, see :doc:`/usage/visualization`. + +Different types of samples +-------------------------- + +Depending on the specific type of geometry +and magnetic order, concrete subclasses of :class:`AbstractSample <tetrax.core.sample.AbstractSample>` can be +generated which are distinguished by their set of possible material parameters (ferromagnetic or antiferromagnetic), +dimensionality, or numerical solvers needed to calculate magnetization dynamics or statics. The type of sample is chosen when creating the sample. At the beginning of this section, a sample was created by +calling :func:`tetrax.create_sample() <tetrax.core.sample.create_sample>`. Per default, this function creates a +ferromagnetic waveguide sample. However, different options are possible. + +.. autofunction:: tetrax.core.sample.create_sample + +We can see from the ``geometry`` parameter, that different types of samples can be selected: + +- ``confined`` samples such as disks, cuboids, spheres or Möbius ribbons. These types of samples are often the subject of `standard` three-dimensional micromagnetic simulations. Only in such samples, an LLG solver can be used. Using a confined-wave dynamic-matrix approach, the normal modes of confined samples can be calculated. +- ``waveguide`` samples are infinitely long in one direction (here, the :math:`z` direction). Moreover, their (equilibrium) magnetization is taken to be translationally invariant in this direction. Such samples are modelled by discretizing only a single cross section of the waveguide. A propagating-wave dynamic-matrix approach can be used to calculate the normal modes as a function of the wave vector :math:`k` along the :math:`z` direction. +- ``layer`` samples consist of one or multiple infinetely extended layers which are modelled by discretizing a line trace along the thickness of the layer(s). The (equilibrium) magnetization is taken to be homogeneous within the layer planes. A propagating-wave dynamic-matrix approach can be used to calculate the normal modes as a function of the wave vector :math:`k` along the :math:`z` direction. + +For all three different types of samples, energy minimizers are implemented to calculated the magnetic ground states. + +.. image:: sample_types.png + :width: 800 + +.. warning:: + At the moment, only ferromagnetic waveguide samples are fully supported. + +Specific geometries +------------------- + +Template geometries (mesh generators) are all available in the :mod:`tetrax.geometries` module. Even though they are implemented in submodules, +all of them can be accessed in the namespace of :mod:`tetrax.geometries`. For examples + +.. code-block:: python + + sample.set_geom(tx.geometries.tube_cross_section(r=20, R=30, lc=3)) + +.. note:: + For a given sample, only such meshes can be set which fit the type of the sample selected. + For example, it is not possible to set a three-dimensional cuboid mesh for a sample which represents the cross section + of an infinitely long waveguide. + +Geometries for confined samples +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To be implemented. + +.. automodule:: tetrax.geometries.geometries3D + :members: + +Geometries for waveguide samples +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. automodule:: tetrax.geometries.geometries2D + :members: + +Geometries for layer samples +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To be implemented. + +.. automodule:: tetrax.geometries.geometries1D + :members: + +Material parameters +------------------- + +Once a sample object is created, being ferro- or antiferromagnetric, it will be initialized with the material parameters of Ni\ :sub:`80`\Fe\ :sub:`20`\ (permalloy). +However, the material parameters can be set individually to match the system in study. + +Ferromagnetic samples +^^^^^^^^^^^^^^^^^^^^^ + +Typing the following command one can easily check the current material parameters as well as some of the +underlaying mesh parameters. + +>>> sample + +.. image:: ../images/sample_with_mesh.png + +Any of the listed parameters can easily be changed using + +.. code-block:: python + + sample.Msat = 796e3 + sample.Aex = 13e-12 + sample.alpha = 0.007 + sample.scale = 10e-9 + +The uniaxial anisotropy can be defined to be homogeneous over the whole sample or to be spatially varying, both in magnitude and direction. +The direction doesn't need to be a unit vector and will not be normalized in the code. +Therefore, the implementation allows to define directions in units of the anisotropy and set the anisotropy constant to 1. +For details please check the next sections. + +Homogeneous uniaxal anisotropy +"""""""""""""""""""""""""""""" + +A homogeneous uniaxial anisotropy can be set using a triplet for the direction and a single constant. +For a single crystal ferromagnetic sample would be like: + +.. code-block:: python + + sample.Ku = 48e3 # in J/m^3 + sample.e_u = [1, 0, 0] # along the x axis + +Easy plane anisotropies can also be defined, by setting the anisotropy constant to negative value. +An example for an xy easy plane would be: + +.. code-block:: python + + sample.Ku = -20e3 # in J/m^3 + sample.e_u = [0, 0, 1] # along the z axis + + +Spatial-dependent uniaxal anisotropy +"""""""""""""""""""""""""""""""""""" + +A spatial dependent uniaxial anisotropy can be defined by a list of triplets defining the +orientation at each node position and by a list of the constants. The template vectorfields in +:mod:`tetrax.vectorfield` can be used to define spatial dependent vectorfields. + +Here is an example for a radial uniaxial anisotropy with a homogeneous anisotropy constant: + +.. code-block:: python + + sample.Ku1 = 2e4 #J/m^3 + sample.e_u = tx.vectorfields.radial(sample.xyz,1) + +The defined vectorfield of the uniaxial anisotropy can be visualized to check if the aimed +spatial dependence has been set or not. This can be done by the :func:`plot() <tetrax.core.sample.AbstractSample.plot>` method. +For further details please check the provided example: :doc:`../examples/round_tube_spatial_dep_anis`. + +Antiferromagnetic samples +^^^^^^^^^^^^^^^^^^^^^^^^^ + +To be implemented. \ No newline at end of file diff --git a/doc/usage/sample_types.png b/doc/usage/sample_types.png new file mode 100644 index 0000000000000000000000000000000000000000..1a289d9ab781448eacc57d537fa6b9512e7b92ac Binary files /dev/null and b/doc/usage/sample_types.png differ diff --git a/doc/usage/tube_cross_section.png b/doc/usage/tube_cross_section.png new file mode 100644 index 0000000000000000000000000000000000000000..31f47eaeea1bd29cf6cbd23324606a9728245ff4 Binary files /dev/null and b/doc/usage/tube_cross_section.png differ diff --git a/doc/usage/tube_segment_cross_section.png b/doc/usage/tube_segment_cross_section.png new file mode 100644 index 0000000000000000000000000000000000000000..9faf7b9b30ceadfda5bb8c5667383d5f913f695e Binary files /dev/null and b/doc/usage/tube_segment_cross_section.png differ diff --git a/doc/usage/visualization.rst b/doc/usage/visualization.rst new file mode 100644 index 0000000000000000000000000000000000000000..7a5464712121d0e7ea5b5d149ba81d3da90115ef --- /dev/null +++ b/doc/usage/visualization.rst @@ -0,0 +1,26 @@ +Visualization +============= + +This chapter will be more eloborate in the future. + +At the moment, samples and experimental setups can be inspected with + +.. code-block:: + + >>> sample.show() + >>> exp.show() + +which will display a 3D view using the `k3d` package. + +.. autofunction:: tetrax.core.sample.AbstractSample.show + +.. autofunction:: tetrax.core.experimental_setup.ExperimentalSetup.show + +It is also possible to plot scalar fields or veftor fields defined on a given sample using the :func:`plot() <tetrax.core.sample.AbstractSample.show>` method of a sample. + +.. code-block:: + + >>> vector_field = tx.vectorfields.helical(sample.xyz, 90, 1) + >>> sample.plot(vector_field) + +.. autofunction:: tetrax.core.sample.AbstractSample.plot diff --git a/setup.py b/setup.py index c42fc29645f885d9b3faadb745e10810b5da7aac..06bcadad609254f250bb9b270a4088e63574ef13 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ extensions = [ setup( name='TetraX', - version='1.0.1', + version='1.1.0', entry_points={'console_scripts': ['TetraX=fem_core:main']}, packages=find_packages(), ext_modules=cythonize(extensions, language_level=3), @@ -72,6 +72,8 @@ setup( "dev": [ "pydata_sphinx_theme", "nbsphinx", + "numpydoc", + "sphinx_copybutton", ] } ) diff --git a/tests/test_relax.py b/tests/test_relax.py index eac317613ca848962570f98a6e0ef4093a3872fc..32aac3ab440df04f32549c5dff379ffd7b2370ec 100644 --- a/tests/test_relax.py +++ b/tests/test_relax.py @@ -1,4 +1,5 @@ import numpy as np + import tetrax @@ -27,8 +28,7 @@ class TestClass: sample.mag = tetrax.vectorfields.helical(sample.xyz, 60, 1) sample.e_u = np.array([0, 0, 1]) sample.Ku1 = -50000 - sample.get_magnetic_tensors() - + exp = tetrax.create_experimental_setup(sample) exp.relax(tol=1e-11) diff --git a/tetrax/__init__.py b/tetrax/__init__.py index a8500a1dd81cdc79287b366717d4a83d22e68dcc..17a20a93eafe2b7147a37971270c50dc96456197 100644 --- a/tetrax/__init__.py +++ b/tetrax/__init__.py @@ -1,3 +1,5 @@ -from .core import create_sample, create_experimental_setup, ExperimentalSetup +from . import geometries from . import vectorfields -from . import geometries \ No newline at end of file +from .core import create_sample, create_experimental_setup, ExperimentalSetup +from .helpers.io import write_field_to_file +from .helpers.math import sample_average diff --git a/tetrax/core/experimental_setup.py b/tetrax/core/experimental_setup.py index 6636b4a755fef90daae0ea74e7582289ec06a64a..fadeb7bf7366c59041f2e4eb66bb87bb0dfca209 100644 --- a/tetrax/core/experimental_setup.py +++ b/tetrax/core/experimental_setup.py @@ -1,7 +1,10 @@ +import os + import k3d import numpy as np import pygmsh +from ..core.mesh import * from ..experiments.calculate_absorption import calculate_absorption from ..experiments.relax import minimize_gibbs_free_energy from ..helpers.math import h_hom, h_CPW, h_strip @@ -10,6 +13,21 @@ B_ext_color = 0xED4B42 class ExperimentalSetup: + """Experimental setup built around a ``sample`` in which external parameters are defined and numerical experiments + can be conducted. + + Attributes + ---------- + sample : AbstractSample + Sample object on which numerical experiments are conducted. + name : str + Name of the experimental setup, used to store results (default is ``"my_experiment"``). + Bext : MeshVector + Vector field of the static external magnetic field (given in T). Can also be set as a triplet, `e.g.` as + ``[0, 0, 0.5]`` for a homogeneous field of 500 mT in :math:`z` direction. + antenna : _AbstractMicrowaveAntenna + Microwave antenna in the experimental setup (see User Guide for details). + """ def __init__(self, sample, name="my_experiment"): self.sample = sample @@ -28,9 +46,9 @@ class ExperimentalSetup: print("This sample does not have a mesh yet. You cannot set any external field for it.") else: if np.array_equal(self.sample.xyz.shape, np.shape(value)): - self._Bext = value + self._Bext = MeshVector(value) elif np.shape(value) == (3,): - self._Bext = np.array([value for i in range(self.sample.nx)]) + self._Bext = MeshVector([value for i in range(self.sample.nx)]) else: print("You are trying to set an external field which does not match the shape of the mesh.") @@ -40,12 +58,23 @@ class ExperimentalSetup: @antenna.setter def antenna(self, value): - if isinstance(value, MicrowaveAntenna): + if isinstance(value, _AbstractMicrowaveAntenna): self._antenna = value else: raise ValueError(f'Input {value} is not of type "MicrowaveAntenna"') def show(self, comp="vec", scale=5, show_antenna=True): + """Shows the experimental setup, including external fields and microwave antennna. + + Parameters + ---------- + comp : {"vec", "x", "y", "z"} + If external field is nonzero, determines which component will be plotted. + scale : float + Determines the scale of the vector glyphs used to visualize the magnetization (default is 5). + show_antenna : bool + If available, show the microwave antenna (default is ``True``). + """ plot = k3d.plot() plt_mesh = k3d.mesh(np.float32(self.sample.mesh.points), np.uint32(self.sample.mesh.get_cells_type("triangle"))) plt_mesh.wireframe = True @@ -55,18 +84,16 @@ class ExperimentalSetup: if np.any(self._Bext) != 0: - hx = self._Bext.T[0] - hy = self._Bext.T[1] - hz = self._Bext.T[2] - h_abs = np.sqrt(hx ** 2 + hy ** 2 + hz ** 2) + h_abs = np.array(np.sqrt(self._Bext.x ** 2 + self._Bext.y ** 2 + self._Bext.z ** 2)) colors = np.repeat(np.array([(np.uint32(B_ext_color), np.uint32(B_ext_color))]), self.sample.nx) if comp == "vec": m_mesh = k3d.vectors(np.float32(self.sample.mesh.points), - np.float32(scale * (np.vstack([hx, hy, hz])).T), + np.float32(scale * (np.vstack([self._Bext.x, self._Bext.y, self._Bext.z])).T), head_size=2.5 * scale * np.mean(h_abs), line_width=0.05 * scale * np.mean(h_abs), colors=colors) else: - comp_dict = {"x": hx, "y": hy, "z": hz, "phi": np.arctan2(hy, hx), + comp_dict = {"x": self._Bext.x, "y": self._Bext.y, "z": self._Bext.z, + "phi": np.arctan2(self._Bext.y, self._Bext.x), "abs": h_abs} color_dict = {"abs": k3d.colormaps.matplotlib_color_maps.Inferno, "x": k3d.colormaps.matplotlib_color_maps.RdBu, @@ -96,26 +123,238 @@ class ExperimentalSetup: plot.display() - def relax(self, tol=1e-12, continue_least_with_squares=False, return_last=False): - # TODO check if sample has magnetization + def relax(self, tol=1e-12, continue_with_least_squares=False, return_last=False, save_mag=True, fname="mag.vtk", + verbose=True): + """Relax the magnetization ``mag`` of a sample in the experimental setup by minimizing the total magnetic energy. + + Parameters + ---------- + tol : float, optional + Tolerance at which the minimization is considered successful (default is ``1e-12``). + continue_with_least_squares : bool + If minimization with conjugate-gradient method was not successful, continue with least-squares method + (default is ``False``). This is more stable, but slower. + return_last : bool + If minimization was not successful, set the last iteration step as the sample magnetization (default is ``False``). + save_mag : bool + Save the resulting magnetization as `vtk` file to the folder of the experimental setup (default is ``True``). + fname : str + Filename for magnetization file (default is ``mag.vtk``). + verbose : bool + Output progress of the calculation (default is ``True``). + + Returns + ------- + success : bool + If the relaxation was successful of not. + + Notes + ----- + For waveguide samples, the equilibration is not totally stable yet. Please use with care and check the resulting + equilibrium states. + + Examples + -------- + - :doc:`Spin-wave dispersion in transversally-magnetized rectangular waveguide </examples/rectangular_waveguide_DE>` + - :doc:`Hysteresis loop calculation </examples/hysteresis_loop>` + - :doc:`Spatially dependent uniaxial anisotropy </examples/round_tube_spatial_dep_anis>` + """ + if self.sample.mag is None: print("Your sample does not have a magnetization field yet.") + return False else: - self.sample.mag = minimize_gibbs_free_energy(self.sample, self._Bext, tol_cg=tol, return_last=return_last, - continue_least_with_squares=continue_least_with_squares) - - def eigenmodes(self, kmin=-40e6, kmax=40e6, Nk=81, num_modes=20, k=None, no_dip=False, num_cpus=1, save_modes=True, - save_local=False, save_magphase=False): + self.sample._get_magnetic_tensors() + self.sample.mag, success = minimize_gibbs_free_energy(self.sample, self._Bext, tol_cg=tol, + return_last=return_last, + continue_with_least_squares=continue_with_least_squares, + verbose=verbose) + if save_mag: + experiment_path = f"./{self.sample.name}/{self.name}" + if not os.path.exists(experiment_path): + os.makedirs(experiment_path) + fname = f"{experiment_path}/{fname}" + self.sample.field_to_file(self.sample.mag, fname=fname, qname="mag") + return success + + def eigenmodes(self, kmin=-40e6, kmax=40e6, Nk=81, num_modes=20, k=None, no_dip=False, h0_dip=False, num_cpus=1, + save_modes=True, + save_local=False, save_magphase=False, perturbed_dispersion=False, save_stiffness_fields=False, + verbose=True): + """Calculate the spin-wave eigenmodes an their frequencies/dispersion of the sample in its current state + using a dynamic matrix approach. + + The eigensystem is calculuated by numerically solving the linearized equation of motion of magnetizion (see + User Guide for details). For waveguides or layer samples, a finite-element propagating-wave dynamic-matrix + approach is used [1]_. The dispersion is returned as a ``pandas.DataFrame``. Additionally, it is saved as + ``dispersion.csv`` (or ``dispersion_perturbedmodes.csv``, if ``perturbed_dispersion=True``) in the folder + of the experimental setup. + + Parameters + ---------- + kmin : float + Minimum wave vector in rad/m (default is -40e6). This argument is ignored when calculating the eigensystem for + confined samples. + kmax : float + Maximum wave vector in rad/m (default is 40e6). This argument is ignored when calculating the eigensystem for + confined samples. + Nk : int + Number of wave-vector values, for which the mode frequencies are calculated. This argument is ignored when calculating the eigensystem for + confined samples (default is 81). + num_modes : int + Determines how many modes (for confined samples) or branches of the dispersion (for waveguides and layers + samples) are calculated in total (default is 20). + k : float + If set to a ``float`` value, the eigensystem is only calculated for a single wave vector (default is ``None``). + This argument is ignored when calculating the eigensystem for + confined samples. + no_dip : bool + Exclude dipolar interaction from calculations (default is ``False``). + h0_dip : bool + If ``no_dip`` is ``True``, still retain dipolar contributions in the equilibrium effective field. + num_cpus : int + Number of CPU cores to be used in parallel for calculations (default is 1). If set to ``-1``, all available + cores are used. + save_modes : bool + Saves real and imaginary part of the mode profiles in cartesian basis as `vtk` files (default is ``True``). + The mode profiles are saved in a ``/mode-profiles`` subdirectory in the folder of the experimental setup. + save_local : bool + If ``save_modes`` is also ``True``, save the components of the mode profiles in the basis locally orthogonal + to the equilibrum magnitization (default is ``False``). The local components are saved in the same ``vtk`` + file as cartesian components. + save_magphase : + If ``save_modes`` is also ``True``, save magnetide and phase of the mode-profile components (default is ``False``). + Saved in the same + perturbed_dispersion : bool + After calculating the eigenmodes/normalmodes (possibly with ``no_dip=True``), calculate the zeroth-order + pertubation of the dispersion including dynamic dipolar fields according to Ref [2]_. Gives approximation + for dispersion without dipolar hybridization (default is ``False``). The results are appended to the returned + ``dispersion`` dataframe. + save_stiffness_fields : bool + If `perturbed_dispersion` is also ``True``, append the unitless individual stiffness fields (components) within the + perturbed dispersion to the ``dispersion`` dataframe (default is ``False``). Allows to numerically reverse + engineer general spin-wave dispersions [2]_. + verbose : bool + Output progress of the calculation (default is ``True``). + + Returns + ------- + dispersion : pandas.DataFrame + Dataframe (table) containing the calculated dispersion. + + Notes + ----- + By default, the ``dispersion`` DataFrame is structured as + + =========== ========= ========= ========= + k (rad/m) f0 (GHz) f1 (GHz) ... + =========== ========= ========= ========= + -40e6 1.426 2.352 ... + ... ... ... ... + =========== ========= ========= ========= + + In case ``perturbed_dispersion=True``, additional columns are added as + + =========== ========= =============== ========= + k (rad/m) f0 (GHz) f_pert_0 (GHz) ... + =========== ========= =============== ========= + -40e6 1.426 1. 484 ... + ... ... ... ... + =========== ========= =============== ========= + + Additionally, if ``save_stiffnessfields=True``, the unitless stiffness fields per magnetic interaction are also appended + + =========== ========= =============== ============= ========= + k (rad/m) f0 (GHz) f_pert_0 (GHz) Re(N21_exc)_0 ... + =========== ========= =============== ============= ========= + -40e6 1.426 1. 484 0.431 ... + ... ... ... ... ... + =========== ========= =============== ============= ========= + + Indivual columns/rows can be obtained in the usual way for `pandas.DataFrame``, for example, with + + >>> k = dispersion["k (rad/m)"] + >>> first_row = dispersion.iloc[0] + + + References + ---------- + .. [1] Körber, *et al.*, "Finite-element dynamic-matrix approach for + spin-wave dispersions in magnonic waveguides with arbitrary + cross section", `AIP Advances 11, 095006 (2021) <https://doi.org/10.1063/5.0054169>`_ + .. [2] Körber and Kákay, "Numerical reverse engineering of general spin-wave dispersions: Bridge between + numerics and analytics using a dynamic-matrix approach", `Phys. Rev. B 104, + 174414 (2021) <https://doi.org/10.1103/PhysRevB.104.174414>`_ + + Examples + -------- + - :doc:`Spin-wave dispersion in thin vortex-state nanotube </examples/round_tube_dispersion_vortex>` + - :doc:`Spin-wave dispersion in transversally-magnetized rectangular waveguide </examples/rectangular_waveguide_DE>` + + """ if self.sample.mag is None: print("Your sample does not have a magnetization field yet.") else: + self.sample._get_magnetic_tensors() dispersion = self.sample.eigensolver(self.sample, self.Bext, self.name, kmin, kmax, Nk, num_modes, k, - no_dip, num_cpus, save_modes, save_local, save_magphase) + no_dip, h0_dip, num_cpus, save_modes, save_local, save_magphase, + perturbed_dispersion, save_stiffness_fields, verbose) self._modes_available = save_modes self.dispersion = dispersion return dispersion - def absorption(self, auto_eigenmodes=False, fmin=0, fmax=30, Nf=200, k=None): + def absorption(self, auto_eigenmodes=False, fmin=0, fmax=30, Nf=200, k=None, verbose=True): + r"""Obtain the microwave absorption of the calculated eigensystem with respect to the microwave ``antenna`` in the + experimental setup. + + Parameters + ---------- + auto_eigenmodes : bool + If no eigenmodes are available in this experimental setup, calculate eigenmodes with default settings. + fmin : float + Minimum microwave frequency in GHz for which the absorption line is calculated (default is 0). + fmax : float + Maximum microwave frequency in GHz for which the absorption line is calculated (default is 30). + Nf : int + Number of microwave frequencies for which the absorption is calculated (default is 200). + k : float + If set to a value, the absorption line is calculated only for a single wave vector (default is ``None``). + Also, the full dynamic susceptibility is saved to a dataframe. This argument is ignored in confined samples. + verbose : bool + Output progress of the calculation (default is ``True``). + + Returns + ------- + susceptibility : pandas.DataFrame + Only if ``k`` is not ``None`` or sample is a confined sample. Contains frequency-dependent real and + imaginary part as well as magnitude as phase and magnitude of the averaged dynamic susceptiblity. + (absorbed_power, wave_vectors, frequencies) : (numpy.ndarray, numpy.ndarray, numpy.ndarray) + If ``k`` is ``None`` and sample is a waveguide or a layer sample. Saves the wave-vector and frequency- + dependent microwave-power absorption (``absorbed_power``) together with the corresponding wave vectors + and frequencies as a 2D arrays. + + Notes + ----- + The return of a wave-vector-dependent absorption calculation can be plotted using `matplotlib` as + + >>> absorbed_power, wave_vectors, frequencies = exp.absorption(...) + >>> import matplotlib.pyplot as plt + >>> plt.pcolormesh(wave_vectors, frequencies, absorbed_power) + + References + ---------- + .. [1] Verba, *et al.*, "Damping of linear spin-wave modes in magnetic nanostructures: Local, nonlocal, + and coordinate-dependent damping", `Phys. Rev. B 98, 104408 (2018) <https://doi.org/10.1103/PhysRevB.98.104408>`_ + + .. [2] Körber, *et al.*, "Symmetry and curvature effects on spin waves in vortex-state hexagonal nanotubes", + `Phys. Rev. B 104, 184429 (2021) <https://doi.org/10.1103/PhysRevB.104.184429>`_ + + Examples + -------- + - :doc:`FMR in a rectangular waveguide </examples/FMR_rect_waveguide>` + - :doc:`Absorption of a nanotube with antennae </examples/round_tube_absorption>` + + """ if self.sample.mag is None: print("Your sample does not have a magnetization field yet.") else: @@ -125,21 +364,26 @@ class ExperimentalSetup: "first or set auto_eigenmodes=True when calling absorption().") if self._modes_available is False and auto_eigenmodes is True: - print("Calculating eigenmodes first.") + if verbose: + print("Calculating eigenmodes first.") self.dispersion = self.eigenmodes(save_modes=True, k=k) - print("Calculating absorption.") + if verbose: + print("Calculating absorption.") power_map = calculate_absorption(self.sample, self.name, self.dispersion, self._antenna, fmin, fmax, Nf, k) - print("Done.") + if verbose: + print("Done.") return power_map if self._modes_available is True and auto_eigenmodes is True: print("Modes are already calculated. Use auto_eigenmodes=False instead.") if self._modes_available is True and auto_eigenmodes is False: - print("Calculating absorption.") + if verbose: + print("Calculating absorption.") power_map = calculate_absorption(self.sample, self.name, self.dispersion, self._antenna, fmin, fmax, Nf, k) - print("Done.") + if verbose: + print("Done.") return power_map @@ -162,13 +406,28 @@ def create_experimental_setup(sample, name: str = "my_experiment") -> Experiment return ExperimentalSetup(sample, name) -class MicrowaveAntenna: +class _AbstractMicrowaveAntenna: def __init__(self): self.type = None -class StriplineAntenna(MicrowaveAntenna): +class StriplineAntenna(_AbstractMicrowaveAntenna): + """Stripline antenna. The antenna is assumed to be infinitely thin. + + .. image:: /usage/exp_antenna_stripline.png + :width: 150 + + Attributes + ---------- + width : float + Width of the current lines (in propagation direction). + gap : float + Gap (not pitch) between the current lines. + spacing : float + Distance between coordiante origin and antenna center plane. + + """ def __init__(self, width, spacing): self.type = "stripline" @@ -190,7 +449,22 @@ class StriplineAntenna(MicrowaveAntenna): return antenna_field_func -class CPWAntenna(MicrowaveAntenna): +class CPWAntenna(_AbstractMicrowaveAntenna): + """Coplanar-waveguide antenna. The antenna is assumed to be infinitely thin. + + .. image:: /usage/exp_antenna_cpw.png + :width: 300 + + Attributes + ---------- + width : float + Width of the current lines (in propagation direction). + gap : float + Gap (not pitch) between the current lines. + spacing : float + Distance between coordiante origin and antenna center plane. + + """ def __init__(self, width, gap, spacing): self.type = "stripline" @@ -220,7 +494,17 @@ class CPWAntenna(MicrowaveAntenna): return antenna_field_func -class HomogeneousAntenna(MicrowaveAntenna): +class HomogeneousAntenna(_AbstractMicrowaveAntenna): + """Antenna which produces a homogeneous microwave field. + + Attributes + ---------- + theta : float + Polar angle of the field polarization (given in degrees). + phi : float + Azimuthal angle of the field polarization (given in degrees). + + """ def __init__(self, theta=0, phi=0): self.type = "homogeneous" @@ -242,7 +526,23 @@ class HomogeneousAntenna(MicrowaveAntenna): return antenna_field_func -class CurrentLoopAntenna(MicrowaveAntenna): +class CurrentLoopAntenna(_AbstractMicrowaveAntenna): + """Antenna with the shape of a current loop. + + .. image:: /usage/exp_antenna_loop.png + :width: 180 + + The current loop is oriented in the :math:`xy` plane. For calculations, the current loop is taken to be infinitely + thin in radial direction. + + Attributes + ---------- + width : float + Width of the antenna (in propagation direction). + radius : float + Radius of the antenna. + + """ def __init__(self, width, radius): self.type = "currentloop" diff --git a/tetrax/core/mesh.py b/tetrax/core/mesh.py new file mode 100644 index 0000000000000000000000000000000000000000..4f5b20c66a2d1b6405773dfaa679dc332a544074 --- /dev/null +++ b/tetrax/core/mesh.py @@ -0,0 +1,468 @@ +""" +This core submodule provides several subclasses of `np.ndarray` to define different scalar and vector +fields on meshes. The import convention + + >>> import numpy as np + +is used. +""" + +import numpy as np + +__all__ = ["MeshScalar", + "MeshVector", + "FlattenedMeshVector", + "FlattenedAFMMeshVector", + "LocalMeshVector", + "FlattenedLocalMeshVector", + "make_flattened_AFM", + ] + + +class MeshScalar(np.ndarray): + """Class for scalar fields defined on a mesh. + + Input `array_like` needs to be one dimensional. The number of nodes `nx` is + inferred from the length of the input array. + + Attributes + ---------- + nx : int + Number of nodes. + """ + + def __new__(cls, input_array): + + obj = np.asarray(input_array).view(cls) + if len(obj.shape) == 1: + obj.nx = obj.shape[0] + return obj + else: + raise ValueError(f"Input {input_array} is not 1-dimensional.") + + +class MeshVector(np.ndarray): + r"""Class for three-component vector fields of shape `(nx, 3)` defined on a mesh. + + The entries of the vector are ordered according to + + .. math:: + + \mathbf{a} = \begin{pmatrix} + a_{x_1} & a_{y_1} & a_{z_1} \\ + & \vdots & \\ + a_{x_N} & a_{y_N} & a_{z_N} \\ + \end{pmatrix} + + while :math:`N` = `nx`. The input `array_like` needs to be two dimensional with `shape[1] = 3` (array of triplets). + The number of nodes `nx` is inferred from `shape[0]` of the input array. + + Attributes + ---------- + nx : int + Number of nodes. + x : MeshScalar + The :math:`x` component of the vector field at each mesh node. + y : MeshScalar + The :math:`y` component of the vector field at each mesh node. + z : MeshScalar + The :math:`z` component of the vector field at each mesh node. + + Methods + ------- + to_flattened() + Returns the vector field as :py:class:`FlattenedMeshVector`. + + See Also + -------- + FlattenedMeshVector, LocalMeshVector, FlattenedLocalMeshVector + """ + + def __new__(cls, input_array): + obj = np.asarray(input_array).view(cls) + if len(obj.shape) == 2: + if obj.shape[1] != 3: + raise ValueError(f"Input {input_array} malshaped. Vector dimension is {obj.shape[1]} and should be 3.") + obj.nx = obj.shape[0] + return obj + else: + raise ValueError(f"Input {input_array} is not 2-dimensional.") + + def __array_finalize__(self, obj): + if obj is None: return + self.nx = getattr(obj, 'nx', None) + + def to_flattened(self): + return FlattenedMeshVector(self.T.flatten()) + + @property + def x(self): + return MeshScalar(self.T[0]) + + @x.setter + def x(self, value): + self[:, 0] = value + + @property + def y(self): + return MeshScalar(self.T[1]) + + @y.setter + def y(self, value): + self[:, 1] = value + + @property + def z(self): + return MeshScalar(self.T[2]) + + @z.setter + def z(self, value): + self[:, 2] = value + + +class FlattenedMeshVector(np.ndarray): + """Class for three-component vector fields of shape `(3*nx,)` defined on a mesh. + + The entries of the vector are ordered according to + + .. math:: \mathbf{a} = (a_{x_1}, ... , a_{x_N}, a_{y_1}, ... , a_{y_N}, a_{z_1}, ... , a_{z_N}) + + while :math:`N` = `nx`. The input `array_like` needs to be one dimensional and cannot be a + :py:class:`FlattenedLocalMeshVector`. The number of nodes `nx` is inferred from the length of the input array. + + Attributes + ---------- + nx : int + Number of nodes. + x : MeshScalar + The :math:`x` component of the vector field at each mesh node. + y : MeshScalar + The :math:`y` component of the vector field at each mesh node. + z : MeshScalar + The :math:`z` component of the vector field at each mesh node. + + Methods + ------- + to_ùnflattened() + Returns the vector field as an unflattened :py:class:`MeshVector`. + + See Also + -------- + MeshVector, LocalMeshVector, FlattenedLocalMeshVector + """ + __array_priority__ = 15 + + def __new__(cls, input_array): + obj = np.asarray(input_array).view(cls) + if len(obj.shape) == 1 and not isinstance(input_array, FlattenedLocalMeshVector): + obj.nx = obj.shape[0] // 3 + return obj + + elif len(obj.shape) == 1 and isinstance(input_array, FlattenedLocalMeshVector): + raise ValueError(f"Input {input_array} is a flattened local mesh vector. " + "These can only be converted to flattened mesh vectors " + "using the inverse of a projection operator.") + else: + raise ValueError(f"Input {input_array} is not 1-dimensional.") + + def __array_finalize__(self, obj): + if obj is None: return + self.nx = getattr(obj, 'nx', None) + + def to_unflattened(self): + return MeshVector(self.reshape(3, self.nx).T) + + @property + def x(self): + return MeshScalar(self[:self.nx]) + + @x.setter + def x(self, value): + self[:self.nx] = value + + @property + def y(self): + return MeshScalar(self[self.nx:-self.nx]) + + @y.setter + def y(self, value): + self[self.nx:-self.nx] = value + + @property + def z(self): + return MeshScalar(self[-self.nx:]) + + @z.setter + def z(self, value): + self[-self.nx:] = value + + +class FlattenedAFMMeshVector(np.ndarray): + r"""Class of shape for antiferromagnets `(6*nx,)` to hold the flattened vector fields of each sublattice + defined on the same mesh. + + The entries of the vector are ordered according to + + .. math:: \mathbf{a} = (a_{x_1}, ... , a_{x_N}, a_{y_1}, ... , a_{y_N}, a_{z_1}, ... , a_{z_N}, b_{x_1}, ... , b_{x_N}, b_{y_1}, ... , b_{y_N}, b_{z_1}, ... , b_{z_N}) + + + while :math:`N` = `nx` and :math:`\mathbf{a}`, :math:`\mathbf{b}` are the vector fields of the respective sublattices. + The input `array_like` needs to be one dimensional. The number of nodes `nx` is inferred from the input array. + + Attributes + ---------- + nx : int + Number of nodes. + x1 : MeshScalar + The :math:`x` component of the first sublattice vector field at each mesh node. + y1 : MeshScalar + The :math:`y` component of the first sublattice vector field at each mesh node. + z1 : MeshScalar + The :math:`z` component of the first sublattice vector field at each mesh node. + x2 : MeshScalar + The :math:`x` component of the second sublattice vector field at each mesh node. + y2 : MeshScalar + The :math:`y` component of the second sublattice vector field at each mesh node. + z2 : MeshScalar + The :math:`z` component of the second sublattice vector field at each mesh node. + + Methods + ------- + to_two_unflattened() + Returns the indivudal vector fields of the sublattices as two separate :py:class:`MeshVector`s. + + See Also + -------- + make_flattened_AFM + """ + __array_priority__ = 15 + + def __new__(cls, input_array): + obj = np.asarray(input_array).view(cls) + if len(obj.shape) == 1 and not isinstance(input_array, FlattenedLocalMeshVector): + obj.nx = obj.shape[0] // 6 + return obj + + elif len(obj.shape) == 1 and isinstance(input_array, FlattenedLocalMeshVector): + raise ValueError(f"Input {input_array} is a flattened local mesh vector. " + "These can only be converted to flattened mesh vectors " + "using the inverse of a projection operator.") + else: + raise ValueError(f"Input {input_array} is not 1-dimensional.") + + def __array_finalize__(self, obj): + if obj is None: return + self.nx = getattr(obj, 'nx', None) + + def to_two_unflattened(self): + mag1 = FlattenedMeshVector(self[:3 * self.nx]).to_unflattened() + mag2 = FlattenedMeshVector(self[3 * self.nx:]).to_unflattened() + return mag1, mag2 + + @property + def x1(self): + return MeshScalar(self[:self.nx]) + + @x1.setter + def x1(self, value): + self[:self.nx] = value + + @property + def y1(self): + return MeshScalar(self[self.nx:2 * self.nx]) + + @y1.setter + def y1(self, value): + self[self.nx:2 * self.nx] = value + + @property + def z1(self): + return MeshScalar(self[2 * self.nx:3 * self.nx]) + + @z1.setter + def z1(self, value): + self[2 * self.nx:3 * self.nx] = value + + @property + def x2(self): + return MeshScalar(self[3 * self.nx:4 * self.nx]) + + @x2.setter + def x2(self, value): + self[3 * self.nx:4 * self.nx] = value + + @property + def y2(self): + return MeshScalar(self[4 * self.nx:5 * self.nx]) + + @y2.setter + def y2(self, value): + self[4 * self.nx:5 * self.nx] = value + + @property + def z2(self): + return MeshScalar(self[5 * self.nx:6 * self.nx]) + + @z2.setter + def z2(self, value): + self[5 * self.nx:6 * self.nx] = value + + +class LocalMeshVector(np.ndarray): + r"""Class for two-component vector fields of shape `(nx, 2)` defined on a mesh. + + This class is used to describe vector fields locally ortoghonal and expressed in the :math:`(u,v,w)` basis attached to the + equilibrium magnetization, hence the name. Because of that, the third (:math:`w`) compontent of the vector field + is always zero and therefore omitted. + The entries of the vector field are ordered according to + + .. math:: + + \mathbf{a} = \begin{pmatrix} + a_{u_1} & a_{v_1} \\ + & \vdots & \\ + a_{u_N} & a_{v_N} \\ + \end{pmatrix} + + while :math:`N` = `nx`. The input `array_like` needs to be one dimensional and cannot be a + :py:class:`FlattenedMeshVector`. The number of nodes `nx` is inferred from the length of the input array. + + Attributes + ---------- + nx : int + Number of nodes. + u : MeshScalar + The first (:math:`u`) component of the vector field at each mesh node. + v : MeshScalar + The second (:math:`v`) component of the vector field at each mesh node. + + Methods + ------- + to_flattened() + Returns the vector field as a :py:class:`FlattenedLocalMeshVector`. + + See Also + -------- + MeshVector, FlattenedMeshVector, LocalMeshVector + """ + + def __new__(cls, input_array): + obj = np.asarray(input_array).view(cls) + if len(obj.shape) == 2: + if obj.shape[1] != 2: + raise ValueError(f"Input {input_array} malshaped. Vector dimension is {obj.shape[1]} and should be 2.") + obj.nx = obj.shape[0] + return obj + else: + raise ValueError(f"Input {input_array} is not 2-dimensional.") + + def to_flattened(self): + return FlattenedLocalMeshVector(self.T.flatten()) + + @property + def u(self): + return MeshScalar(self.T[0]) + + @u.setter + def u(self, value): + self[:, 0] = value + + @property + def v(self): + return MeshScalar(self.T[1]) + + @v.setter + def v(self, value): + self[:, 1] = value + + +class FlattenedLocalMeshVector(np.ndarray): + r"""Class for two-component vector fields of shape `(2*nx,)` defined on a mesh. + + This class is used to describe vector fields locally ortoghonal and expressed in the :math:`(u,v,w)` basis attached to the + equilibrium magnetization, hence the name. Because of that, the third (:math:`w`) compontent of the vector field + is always zero and therefore omitted. + The entries of the vector field are ordered according to + + .. math:: \mathbf{a} = (a_{u_1}, ... , a_{u_N}, a_{v_1}, ... , a_{v_N}) + + while :math:`N` = `nx`. The input `array_like` needs to be two dimensional with `shape[1] = 2` (array of triplets). + The number of nodes `nx` is inferred from `shape[0]` of the input array. + + Attributes + ---------- + nx : int + Number of nodes. + u : MeshScalar + The first (:math:`u`) component of the vector field at each mesh node. + v : MeshScalar + The second (:math:`v`) component of the vector field at each mesh node. + + Methods + ------- + to_unflattened() + Returns the vector field as an unflattened :py:class:`LocalMeshVector`. + + See Also + -------- + MeshVector, FlattenedMeshVector, FlattenedLocalMeshVector + """ + + def __new__(cls, input_array): + obj = np.asarray(input_array).view(cls) + if len(obj.shape) == 1 and not isinstance(input_array, FlattenedMeshVector): + obj.nx = obj.shape[0] // 2 + return obj + + elif len(obj.shape) == 1 and isinstance(input_array, FlattenedMeshVector): + raise ValueError(f"Input {input_array} is a flattened mesh vector. " + "These can only be converted to flattened local mesh vectors " + "using a projection operator.") + else: + raise ValueError(f"Input {input_array} is not 1-dimensional.") + + def to_unflattened(self): + return LocalMeshVector(self.reshape(2, self.nx).T) + + @property + def u(self): + return MeshScalar(self[:self.nx]) + + @u.setter + def u(self, value): + self[:self.nx] = value + + @property + def v(self): + return MeshScalar(self[self.nx:]) + + @v.setter + def v(self, value): + self[self.nx:] = value + + +def make_flattened_AFM(a, b): + """Creates a :py:class:`FlattenedAFMMeshVector` from two individual :py:class:`MeshVector`s. + + Parameters + ---------- + a : MeshVector + Vector field of the first sublattice. + b : MeshVector + Vector field of the second sublattice. + + Returns + ------- + FlattenedAFMMeshVector + Flattened vector field of the AFM lattice. + + See Also + -------- + FlattenedAFMMeshVector + """ + a = MeshVector(a) + b = MeshVector(b) + afm_mag = FlattenedAFMMeshVector( + np.concatenate((a.to_flattened(), b.to_flattened())) + ) + return afm_mag diff --git a/tetrax/core/operators.py b/tetrax/core/operators.py index a3ddaeb23a01d21827f1cf98d37e07b1a8a3694f..f72f9afab6f5a95c14451e2846c6db56f47bad7e 100644 --- a/tetrax/core/operators.py +++ b/tetrax/core/operators.py @@ -188,6 +188,7 @@ class CubicAnisotropyLinearOperator(LinearOperator): self.nx = sample.nx self.m0 = sample.mag self.fac = 2 * self.Kc / (mu_0 * self.Msat ** 2) + self.sample = sample # homogenous anisotropy self.c1 = np.repeat(np.array(sample.v1Kc), self.nx, axis=0).flatten() @@ -197,10 +198,13 @@ class CubicAnisotropyLinearOperator(LinearOperator): self.C2 = flattened_mesh_vec_tensor_product(self.c2, self.c2) self.C3 = flattened_mesh_vec_tensor_product(self.c3, self.c3) - self.sparse_mat = self.make_sparse_mat() - self.shape = self.sparse_mat.shape + self.sparse_mat = None + self.shape = (3 * self.nx, 3 * self.nx) + + def make_sparse_mat_with_current_m0(self): + # get current m0 + self.m0 = self.sample.mag.to_flattened() - def make_sparse_mat(self): # create bare dyads M0 = flattened_mesh_vec_tensor_product(self.m0, self.m0) @@ -218,7 +222,7 @@ class CubicAnisotropyLinearOperator(LinearOperator): Nc2 = flattened_mesh_vec_tensor_product(A2 * self.c2, self.c2) + 2 * self.C1.dot(M0).dot(self.C2 + self.C3) Nc3 = flattened_mesh_vec_tensor_product(A1 * self.c3, self.c3) + 2 * self.C1.dot(M0).dot(self.C2 + self.C3) - return self.fac * (Nc1 + Nc2 + Nc3) + self.sparse_mat = self.fac * (Nc1 + Nc2 + Nc3) def set_new_param(self, conf, nx): """ diff --git a/tetrax/core/sample.py b/tetrax/core/sample.py index 85192800f800be0fec3799c528d45d5aaf993185..610a5a1439b56f00d62c3945b96dc62d862d9074 100644 --- a/tetrax/core/sample.py +++ b/tetrax/core/sample.py @@ -6,14 +6,47 @@ import numpy as np from .fem_core.cythoncore import get_matrices from .operators import ExchangeOperator, DipolarOperator, UniAxialAnisotropyOperator, BulkDMIOperator, \ - InterfacialDMIOperator + InterfacialDMIOperator, CubicAnisotropyLinearOperator +from ..core.mesh import * from ..experiments.eigen import calculate_normal_modes, not_implemented_eigensolver from ..helpers.io import check_mesh_shape -from ..helpers.io import get_mesh_dimension +from ..helpers.io import get_mesh_dimension, write_field_to_file from ..helpers.math import normalize_single_3d_vec +qty_color_cycle = [0x0072B2, 0xE34234, 0x009E73, 0xE69F00, 0x56B4E9] + class AbstractSample(ABC): + """Base class for the concrete sample classes + + - :class:`_ConfinedSampleFM` + - :class:`_ConfinedSampleAFM` + - :class:`_WaveguideSampleFM` + - :class:`_WaveguideSampleAFM` + - :class:`_LayerSampleFM` + - :class:`_LayerSampleAFM` + + which are created by multiple-inheritance from this base class together with mixin classes for magnetic order (FM + or AFM) and geometry type (confined, waveguide, layer). The available attributes for material parameters depend on + the magnetic order (see User Guide for details). + + Attributes + ---------- + name : str + Name of the sample. + nx : int + Total number of nodes in the mesh of the sample. + nb : int + Number of boundary nodes in the mesh of the sample. + mesh : meshio.Mesh + Mesh of the sample. + xyz : MeshVector + Coordinates on the mesh of the sample. + scale : float + Scale of the sample (default is `1e-9`, such that all spatial dimensions are given in nanometer). + + + """ def __init__(self, name, geometry, magnetic_order, dim): self.name = name @@ -29,31 +62,162 @@ class AbstractSample(ABC): self.scale = 1e-9 self.xyz = None - self.get_eigensolver() - self.initialize_material_params() + self._get_eigensolver() + self._initialize_material_params() @abstractmethod - def get_eigensolver(self): + def _get_eigensolver(self): """This method should be implemented by the FerromagneticMixin or AntiferromagneticMixin class of the concrete sample.""" @abstractmethod - def initialize_material_params(self): + def _initialize_material_params(self): """This method should be implemented by the FerromagneticMixin or AntiferromagneticMixin class of the concrete sample.""" @abstractmethod - def get_magnetic_tensors(self): + def _get_magnetic_tensors(self): """This method should be implemented by the FerromagneticMixin or AntiferromagneticMixin class of the concrete sample.""" @abstractmethod - def show(self): - """This method should be implemented by the FerromagneticMixin or - AntiferromagneticMixin class of the concrete sample.""" + def show(self, show_node_labels=False, show_mag=True, comp="vec", scale=5): + """Show the sample. Displays the mesh and, if available, the magnetization(s). + + Parameters + ---------- + show_node_labels : bool + If true, shows the label associated to each node. + show_mag : bool + Show the magnetization (or magnetizations, in the case of antiferromagnetic samples). + comp : {"vec", "x", "y", "z"} + Determines which component will be plotted. + scale : float + Determines the scale of the vector glyphs used to visualize the magnetization (default is 5). + + + Returns + ------- + + """ + + def plot(self, fields, comp="vec", scale=5, labels=None): + """ + Plot a vector or scalar field which is defined on each node of the mesh. Multiple fields can be plotted at the same time. + + Parameters + ---------- + fields : numpy.array or list(numpy.array) + Scalar field of shape `(nx,)`, vector field of shape `(nx,3)` or list containing any choice of + the aforementioned. + comp : {"vec", "x", "y", "z"} + If `field` is a vector field, this parameter determines which component will be plotted. + scale : float + Determines the scale of the vector glyphs used to visualize a vectorfield (default is 5). + labels : str or list(str) + Label(s) of the vectorfield(s) to be plotted. If ``labels`` is a list, it needs to have the same length as + ``fields`` (default is ``None``). + """ + cmap = k3d.colormaps.matplotlib_color_maps.Viridis + + if self.mesh == None: + print("This sample does not have a mesh yet.") + else: + + # convert input to list if it is not already + if isinstance(fields, np.ndarray): + fields = [fields] + + if isinstance(labels, str): + labels = [labels] + + # do some tests on the input + if labels is not None: + if len(labels) != len(fields): + raise ValueError( + f"You supplied labels for the fields you want to plot, but the number of labels" + f" ({len(labels)}) does not match the number of fields ({len(fields)}).") + + # plot the mesh itself + plot = k3d.plot(lighting=0) + plt_mesh = k3d.mesh(np.float32(self.mesh.points), np.uint32(self.mesh.get_cells_type("triangle"))) + plt_mesh.wireframe = True + plt_mesh.color = 0xaaaaaa + plt_mesh.opacity = 1 + plot += plt_mesh + + for i, field in enumerate(fields): + + if not isinstance(field, np.ndarray): + raise ValueError( + f"You are trying to plot something ({field}) which is not a numpy array or a sublcass thereof.") + + if field.shape in [(self.nx, 3), (self.nx,)]: + + label = labels[i] if labels is not None else None + + color = qty_color_cycle[i % len(qty_color_cycle)] + colors = np.repeat(np.array([(np.uint32(color), np.uint32(color))]), self.nx) + + if label is not None: + print(label) + plot += k3d.text2d(label, + position=[i / 2 / len(labels), 0], + color=color, size=1) + + if field.shape == (self.nx,): + # scalar field case + plot += k3d.mesh(self.mesh.points, self.mesh.get_cells_type("triangle"), + attribute=field, + color_map=cmap, interpolation=False, side="double", + flat_shading=True) + + elif field.shape == (self.nx, 3): + + field = MeshVector(field) + + if comp == "vec": + plot += k3d.vectors(np.float32(self.mesh.points), + np.float32(scale * (np.vstack([field.x, field.y, field.z])).T), + head_size=2.5 * scale, + line_width=0.05 * scale, colors=colors) + + + else: + comp_dict = {"x": field.x, "y": field.y, "z": field.z, "phi": np.arctan2(field.y, field.z), + "abs": np.sqrt(field.x ** 2 + field.y ** 2 + field.z ** 2)} + color_dict = {"abs": k3d.colormaps.matplotlib_color_maps.Inferno, + "x": k3d.colormaps.matplotlib_color_maps.RdBu, + "y": k3d.colormaps.matplotlib_color_maps.RdBu, + "z": k3d.colormaps.matplotlib_color_maps.RdBu, + "phi": k3d.colormaps.matplotlib_color_maps.Twilight_shifted} + + # TODO check if comp is valid key + plot += k3d.mesh(self.mesh.points, self.mesh.get_cells_type("triangle"), + attribute=comp_dict[comp], + color_map=color_dict[comp], interpolation=False, side="double", + flat_shading=True) + + + + else: + raise ValueError( + f"The vector field you are trying to plot is of shape {field.shape}, but should be either (nx,3) or (nx,).") + + plot.display() def set_geom(self, mesh): + """Set the geometry/mesh of the sample and do neccesary preprocessing for later calculations. + + According to the dimension of the sample, the mesh is checked for compatibility. After sucessefully setting + the mesh and extrancting necessary information, such as the number of nodes ``nx``, the + coordinates of the mesh vertices ``xyz`` or the normal vectors ``nv``, the discretized differential operators + (``poisson``, ``div_x``, ...) are calculated and the magnetic tensors are set up. + Parameters + ---------- + mesh : meshio.Mesh + """ input_mesh_dim = get_mesh_dimension(mesh) if input_mesh_dim != self._dim: raise ValueError(f"The dimension of the input mesh ({input_mesh_dim}) does " @@ -68,7 +232,7 @@ class AbstractSample(ABC): print("Setting geometry and calculating discretized differential operators on mesh.") self.mesh = mesh - self.xyz = self.mesh.points + self.xyz = MeshVector(self.mesh.points) self.nx = len(self.xyz) self.boundary_nodes, \ @@ -97,19 +261,30 @@ class AbstractSample(ABC): self.div_y *= -1 self.grad_x *= -1 self.grad_y *= -1 - self.get_magnetic_tensors() + self._get_magnetic_tensors() print("Done.") def read_mesh(self, fname): + """Read a mesh from a file using `meshio` and set it as the geometry of a sample. + + Parameters + ---------- + fname : str + Name of the mesh file. + """ try: mesh = meshio.read(fname) except ReadError: print("Could not read this mesh file.") self.set_geom(mesh) + def field_to_file(self, field, fname="field.vtk", qname=None): + """Calls the :func:`tetrax.helpers.io.write_field_to_file` function to write scalar- or vector field to a file.""" + write_field_to_file(field, self, fname, qname) + -class FerromagnetMixin: - def initialize_material_params(self): +class _FerromagnetMixin: + def _initialize_material_params(self): # material parameters self.Msat = 796e3 @@ -144,20 +319,21 @@ class FerromagnetMixin: else: if self.xyz.shape == np.shape(value): # TODO make unitary - self._mag = normalize_single_3d_vec(value) + self._mag = MeshVector(normalize_single_3d_vec(value)) elif np.shape(value) == (3,): - self._mag = normalize_single_3d_vec(np.array([value for i in range(self.nx)])) + self._mag = MeshVector(normalize_single_3d_vec(np.array([value for i in range(self.nx)]))) else: print("You are trying to set a magnetization field which does not match the shape of the mesh. \n" + \ "mesh shape: {}\n".format(self.xyz.shape) + \ "value shape: {}\n".format(np.shape(value))) - def get_magnetic_tensors(self): + def _get_magnetic_tensors(self): self.N_exc = ExchangeOperator(self) self.N_dip = DipolarOperator(self) self.N_uni = UniAxialAnisotropyOperator(self) self.N_DMI = BulkDMIOperator(self) self.N_iDMI = InterfacialDMIOperator(self) + self.N_cub = CubicAnisotropyLinearOperator(self) def show(self, show_node_labels=False, comp="vec", scale=5, show_mag=True): if self.mesh == None: @@ -176,17 +352,16 @@ class FerromagnetMixin: plot += text_obj if np.all(self._mag) is not None and show_mag: - mx = self._mag.T[0] - my = self._mag.T[1] - mz = self._mag.T[2] if comp == "vec": - m_mesh = k3d.vectors(np.float32(self.mesh.points), np.float32(scale * (np.vstack([mx, my, mz])).T), + m_mesh = k3d.vectors(np.float32(self.mesh.points), + np.float32(scale * (np.vstack([self._mag.x, self._mag.y, self._mag.z])).T), head_size=2.5 * scale, line_width=0.05 * scale) else: - comp_dict = {"x": mx, "y": my, "z": mz, "phi": np.arctan2(my, mx), - "abs": np.sqrt(mx ** 2 + my ** 2 + mz ** 2)} + comp_dict = {"x": self._mag.x, "y": self._mag.y, "z": self._mag.z, + "phi": np.arctan2(self._mag.y, self._mag.x), + "abs": np.sqrt(self._mag.x ** 2 + self._mag.y ** 2 + self._mag.z ** 2)} color_dict = {"abs": k3d.colormaps.matplotlib_color_maps.Inferno, "x": k3d.colormaps.matplotlib_color_maps.RdBu, "y": k3d.colormaps.matplotlib_color_maps.RdBu, @@ -229,8 +404,8 @@ class FerromagnetMixin: <td>{self.Ku1} J/m<sup>3</sup></td> </tr> <tr> - <td>Unaxial anistropy angles </td><td><tt>k_phi</tt><br><tt>k_theta</tt></td> - <td>{self.k_phi}°<br>{self.k_theta}°</td> + <td>Unaxial anistropy direction </td><td><tt>e_u</tt></td> + <td>{self.e_u!r}</td> </tr> <tr> <td>Cubic anistropy constant</td><td><tt>Kc1</tt></td> @@ -273,8 +448,8 @@ class FerromagnetMixin: </p>""" -class AntiferromagnetMixin: - def initialize_material_params(self): +class _AntiferromagnetMixin: + def _initialize_material_params(self): self.params = ["a", "couple", "of", "antiferromagnetic", "parameters"] self._mag1 = None @@ -296,49 +471,49 @@ class AntiferromagnetMixin: def mag2(self, value): self._mag2 = value - def get_magnetic_tensors(self): + def _get_magnetic_tensors(self): print("Antiferromagnetic tensors are not implemented yet.") def show(self): pass -class PropagatingWaveEigensolverMixin: - def get_eigensolver(self): +class _PropagatingWaveEigensolverMixin: + def _get_eigensolver(self): self.eigensolver = calculate_normal_modes -class ConfinedWaveEigensolverMixin: - def get_eigensolver(self): +class _ConfinedWaveEigensolverMixin: + def _get_eigensolver(self): self.eigensolver = not_implemented_eigensolver -class ConfinedSampleFM(ConfinedWaveEigensolverMixin, FerromagnetMixin, AbstractSample): +class _ConfinedSampleFM(_ConfinedWaveEigensolverMixin, _FerromagnetMixin, AbstractSample): def description(self): print("I am a ferromagnetic confined sample.") -class ConfinedSampleAFM(ConfinedWaveEigensolverMixin, AntiferromagnetMixin, AbstractSample): +class _ConfinedSampleAFM(_ConfinedWaveEigensolverMixin, _AntiferromagnetMixin, AbstractSample): def description(self): print("I am a antiferromagnetic confined sample.") -class WaveguideSampleFM(PropagatingWaveEigensolverMixin, FerromagnetMixin, AbstractSample): +class _WaveguideSampleFM(_PropagatingWaveEigensolverMixin, _FerromagnetMixin, AbstractSample): def description(self): print("I am a ferromagnetic waveguide sample.") -class WaveguideSampleAFM(PropagatingWaveEigensolverMixin, AntiferromagnetMixin, AbstractSample): +class _WaveguideSampleAFM(_PropagatingWaveEigensolverMixin, _AntiferromagnetMixin, AbstractSample): def description(self): print("I am a antiferromagnetic waveguide sample.") -class LayerSampleFM(PropagatingWaveEigensolverMixin, FerromagnetMixin, AbstractSample): +class _LayerSampleFM(_PropagatingWaveEigensolverMixin, _FerromagnetMixin, AbstractSample): def description(self): print("I am a ferromagnetic layer sample.") -class LayerSampleAFM(PropagatingWaveEigensolverMixin, AntiferromagnetMixin, AbstractSample): +class _LayerSampleAFM(_PropagatingWaveEigensolverMixin, _AntiferromagnetMixin, AbstractSample): def description(self): print("I am a antiferromagnetic layer sample.") @@ -366,12 +541,12 @@ def create_sample(name="my_sample", geometry="waveguide", magnetic_order="FM"): # Which sample type to choose depending on magnetic_order and geometry. - SAMPLE_DICT = {("confined", "FM"): ConfinedSampleFM(name, geometry, magnetic_order, 3), - ("confined", "AFM"): ConfinedSampleAFM(name, geometry, magnetic_order, 3), - ("waveguide", "FM"): WaveguideSampleFM(name, geometry, magnetic_order, 2), - ("waveguide", "AFM"): WaveguideSampleAFM(name, geometry, magnetic_order, 2), - ("layer", "FM"): LayerSampleFM(name, geometry, magnetic_order, 1), - ("layer", "AFM"): LayerSampleAFM(name, geometry, magnetic_order, 1), + SAMPLE_DICT = {("confined", "FM"): _ConfinedSampleFM(name, geometry, magnetic_order, 3), + ("confined", "AFM"): _ConfinedSampleAFM(name, geometry, magnetic_order, 3), + ("waveguide", "FM"): _WaveguideSampleFM(name, geometry, magnetic_order, 2), + ("waveguide", "AFM"): _WaveguideSampleAFM(name, geometry, magnetic_order, 2), + ("layer", "FM"): _LayerSampleFM(name, geometry, magnetic_order, 1), + ("layer", "AFM"): _LayerSampleAFM(name, geometry, magnetic_order, 1), } # check if supplied parameters are valid. diff --git a/tetrax/experiments/eigen.py b/tetrax/experiments/eigen.py index a65342faaf5bcb138d3f8f52ee64b9a5e1d0fe4a..9395ed69acae022035a6652404afc94ea1f2fb93 100644 --- a/tetrax/experiments/eigen.py +++ b/tetrax/experiments/eigen.py @@ -15,7 +15,8 @@ from ..core.operators import cross_operator, TotalDynMat, SuperLUInv, InvTotalDy from ..helpers.math import flattened_mesh_vec_scalar_product, diag_matrix_from_vec -def not_implemented_eigensolver(sample, Bext, exp_name, kmin=-40e6, kmax=40e6, Nk=81, num_modes=20, k=None, no_dip=False,num_cpus=1, save_modes=False,save_local=False, save_magphase=False): +def not_implemented_eigensolver(sample, Bext, exp_name, kmin=-40e6, kmax=40e6, Nk=81, num_modes=20, k=None, + no_dip=False, num_cpus=1, save_modes=False, save_local=False, save_magphase=False): print("This eigensolver will be implemented in a future release.") @@ -24,18 +25,21 @@ def test_rotation(mag, rotation, nx): Test of rotation matrix and equilibrium vector match, i.e. rotation rotates mag into the z direction. """ test_ez = rotation.dot(mag) - e_z = np.concatenate([np.zeros(2*nx), np.ones(nx)]) + e_z = np.concatenate([np.zeros(2 * nx), np.ones(nx)]) if not np.allclose(test_ez, e_z): raise ValueError("Rotation matrix and equilibrium files don't belong together. " "Please create a new rotation matrix.") -def calculate_normal_modes(sample, Bext, exp_name, kmin=-40e6, kmax=40e6, Nk=81, num_modes=20, k=None, no_dip=False,num_cpus=1, save_modes=False,save_local=False, save_magphase=False): +def calculate_normal_modes(sample, Bext, exp_name, kmin=-40e6, kmax=40e6, Nk=81, num_modes=20, k=None, no_dip=False, + h0_dip=False, + num_cpus=1, save_modes=False, save_local=False, save_magphase=False, + perturbed_dispersion=False, save_stiffness_fields=False, verbose=True): """ 2D version """ - modes_path = "./{}/{}/mode-profiles".format(sample.name,exp_name) + modes_path = "./{}/{}/mode-profiles".format(sample.name, exp_name) if not os.path.exists(modes_path): os.makedirs(modes_path) @@ -45,49 +49,47 @@ def calculate_normal_modes(sample, Bext, exp_name, kmin=-40e6, kmax=40e6, Nk=81, N_uni = sample.N_uni N_DMI = sample.N_DMI N_iDMI = sample.N_iDMI - + N_cub = sample.N_cub N_iDMI.set_k(0) N_DMI.set_k(0) N_exc.set_k(0) N_dip.set_k(0) + N_cub.make_sparse_mat_with_current_m0() mu0_cross = cross_operator(sample.nx) - mag = sample.mag.T.flatten() + mag = sample.mag.to_flattened() rotation = rotation_matrix_py(mag) test_rotation(mag, rotation, sample.nx) - nx = sample.nx - hext = Bext.T.flatten() / (mu_0 * sample.Msat) + hext = Bext.to_flattened() / (mu_0 * sample.Msat) hexc = -N_exc.dot(mag) hdip = -N_dip.dot(mag) hDMI = -N_DMI.dot(mag) hiDMI = -N_iDMI.dot(mag) hani = -N_uni.dot(mag) - #hcub = N_cub.nonlinear_field(mag) + hcub = N_cub.nonlinear_field(mag) if no_dip: - heff = hexc + hext + hani + hDMI + hdip + hiDMI # ++ hcub + heff = hexc + hext + hani + hDMI + hdip + hiDMI + hcub else: - heff = hexc + hdip + hext + hani + hDMI + hiDMI #+ hcub + heff = hexc + hdip + hext + hani + hDMI + hiDMI + hcub h0 = flattened_mesh_vec_scalar_product(heff, mag) rotation = rotation[:2 * nx, :] rotation_T = rotation.transpose() - # create k and convert to mesh units if k == None: - k_ = np.linspace(kmin,kmax,Nk) * sample.scale + k_ = np.linspace(kmin, kmax, Nk) * sample.scale else: k_ = [k * sample.scale] - v0 = np.full(2*nx, 1+1j, dtype=complex) - + v0 = np.full(2 * nx, 1 + 1j, dtype=complex) D_tot = TotalDynMat(N_exc.sparse_mat, N_dip, 1j * mu0_cross[:2 * nx, :2 * nx].dot(rotation), rotation_T) @@ -95,7 +97,9 @@ def calculate_normal_modes(sample, Bext, exp_name, kmin=-40e6, kmax=40e6, Nk=81, bndint_order = 6 - modes_per_k_partial = partial(modes_per_k, N_exc, N_uni, N_DMI, N_iDMI, h0, Lambda, rotation, rotation_T, bndint_order, D_tot, v0, no_dip, sample.scale, sample.Msat, sample.gamma, sample, num_modes, modes_path, save_modes,save_local, save_magphase) + modes_per_k_partial = partial(modes_per_k, N_exc, N_uni, N_cub, N_DMI, N_iDMI, h0, Lambda, rotation, rotation_T, + bndint_order, D_tot, v0, no_dip, sample.scale, sample.Msat, sample.gamma, sample, + num_modes, modes_path, save_modes, save_local, save_magphase) if num_cpus == 0 or num_cpus < -1: num_cpus = 1 @@ -110,28 +114,28 @@ def calculate_normal_modes(sample, Bext, exp_name, kmin=-40e6, kmax=40e6, Nk=81, else: pass - with mp.Pool(processes=num_cpus) as p: res_list = [] - with tqdm.tqdm(total=len(k_)) as pbar: + with tqdm.tqdm(total=len(k_), disable=(not verbose)) as pbar: for i, res in enumerate(p.imap_unordered(modes_per_k_partial, k_)): res_list.append(res) pbar.update() - df_freqs = pd.concat(res_list,sort=False).sort_values("k (rad/m)").reset_index(drop=True).fillna("nan") + df_freqs = pd.concat(res_list, sort=False).sort_values("k (rad/m)").reset_index(drop=True).fillna("nan") return df_freqs -def modes_per_k(N_exc, N_uni, N_DMI, N_iDMI, h0, Lambda, rotation, rotation_T, bndint_order, D_tot, v0, no_dip, scale, Msat, gamma,sample, num_modes, modes_path, save_modes, save_local, save_magphase, k): +def modes_per_k(N_exc, N_uni, N_cub, N_DMI, N_iDMI, h0, Lambda, rotation, rotation_T, bndint_order, D_tot, v0, no_dip, + scale, + Msat, gamma, sample, num_modes, modes_path, save_modes, save_local, save_magphase, k): N_exc.set_k(k) N_DMI.set_k(k) N_iDMI.set_k(k) - N_nodip_k_mat = N_exc.sparse_mat + N_uni.sparse_mat + N_DMI.sparse_mat + N_iDMI.sparse_mat + N_nodip_k_mat = N_exc.sparse_mat + N_uni.sparse_mat + N_DMI.sparse_mat + N_iDMI.sparse_mat + N_cub.sparse_mat N_nodip_k_mat += diag_matrix_from_vec(np.tile(h0, 3)) dyn_mat_k = 1j * Lambda.dot(rotation).dot(N_nodip_k_mat).dot(rotation_T) - dyn_mat_k_csc = csc_matrix(dyn_mat_k) if no_dip: @@ -157,8 +161,7 @@ def modes_per_k(N_exc, N_uni, N_DMI, N_iDMI, h0, Lambda, rotation, rotation_T, b error_success = this_error.already_sucess ef = np.zeros(num_modes) - eigenvectors = np.zeros((2*D_tot.nx, num_modes)) - + eigenvectors = np.zeros((2 * D_tot.nx, num_modes)) ef_org = ef.copy() @@ -174,7 +177,6 @@ def modes_per_k(N_exc, N_uni, N_DMI, N_iDMI, h0, Lambda, rotation, rotation_T, b k_m = k / scale - nx = sample.nx cells = sample.mesh.cells xyz = sample.xyz @@ -190,13 +192,13 @@ def modes_per_k(N_exc, N_uni, N_DMI, N_iDMI, h0, Lambda, rotation, rotation_T, b ev_lab_y = ev_lab[nx:-nx] ev_lab_z = ev_lab[-nx:] - save_dict = {"Re(m_x)" : ev_lab_x.real, - "Im(m_x)" : ev_lab_x.imag, - "Re(m_y)" : ev_lab_y.real, - "Im(m_y)" : ev_lab_y.imag, - "Re(m_z)" : ev_lab_z.real, + save_dict = {"Re(m_x)": ev_lab_x.real, + "Im(m_x)": ev_lab_x.imag, + "Re(m_y)": ev_lab_y.real, + "Im(m_y)": ev_lab_y.imag, + "Re(m_z)": ev_lab_z.real, "Im(m_z)": ev_lab_z.imag, - } + } if save_local: ev_1 = ev[:nx] @@ -222,7 +224,7 @@ def modes_per_k(N_exc, N_uni, N_DMI, N_iDMI, h0, Lambda, rotation, rotation_T, b save_dict["Arg(m_y)"] = np.angle(ev_lab_y) save_dict["Arg(m_z)"] = np.angle(ev_lab_z) - meshio.write_points_cells(filename="{}/mode_k{}radperum_{:03d}.vtk".format(modes_path,k_m * 1e-6, i), + meshio.write_points_cells(filename="{}/mode_k{}radperum_{:03d}.vtk".format(modes_path, k_m * 1e-6, i), points=xyz, cells=cells, point_data=save_dict @@ -233,4 +235,4 @@ def modes_per_k(N_exc, N_uni, N_DMI, N_iDMI, h0, Lambda, rotation, rotation_T, b df_k = pd.DataFrame(columns=["k (rad/m)"] + ["f" + str(j) + " (GHz)" for j in range(len(ef))]) df_k.loc[0] = [k_m] + np.real(ef).tolist() - return df_k \ No newline at end of file + return df_k diff --git a/tetrax/experiments/relax.py b/tetrax/experiments/relax.py index f2cce0a280d6465a2b9ba3a1fee197050fdb95a6..8cb2de3747a4fc2d7678a32a55b3a823d6e25aa1 100644 --- a/tetrax/experiments/relax.py +++ b/tetrax/experiments/relax.py @@ -3,12 +3,11 @@ import time import numpy as np from scipy.constants import mu_0 from scipy.optimize import minimize -from scipy.sparse import csr_matrix from ..helpers.math import flattened_mesh_vec_scalar_product, spherical_angles_to_mesh_vector -def not_implemented_minimizer(sample, Bext, tol_cg, return_last=False, continue_least_with_squares=False): +def not_implemented_minimizer(sample, Bext, tol_cg, return_last=False, continue_with_least_squares=False): print("This energy minimizer will be implemented in a future release.") @@ -28,7 +27,8 @@ def energy_per_length_spherical(mag_spherical, nx, dA, h_ext, N, N_cub): # print(mag.shape) # print((h_ext - 0.5 * N.dot(mag)).shape) # energy_per_volume = -tetramagvec_scalar_product(mag, h_ext + 0.5*N_cub.nonlinear_field(mag) - 0.5 * N.dot(mag)).real - energy_per_volume = -flattened_mesh_vec_scalar_product(mag, h_ext - 0.5 * N.dot(mag)).real + energy_per_volume = -flattened_mesh_vec_scalar_product(mag, h_ext - 0.5 * N.dot(mag) + 0.5 * N_cub.nonlinear_field( + mag)).real return (energy_per_volume * dA).sum() @@ -52,7 +52,7 @@ def jac_spherical(mag_spherical, nx, dA, h_ext, N, N_cub): m_z = np.cos(theta) mag = np.concatenate((m_x, m_y, m_z)) - h_eff = h_ext - N.dot(mag).real # + N_cub.nonlinear_field(mag) + h_eff = h_ext - N.dot(mag).real + N_cub.nonlinear_field(mag) # TODO include again! h_x = h_eff[:nx] @@ -72,7 +72,8 @@ def jac_spherical(mag_spherical, nx, dA, h_ext, N, N_cub): return np.concatenate((dw_theta, dw_phi)) -def minimize_gibbs_free_energy(sample, Bext, tol_cg, return_last=False, continue_least_with_squares=False): +def minimize_gibbs_free_energy(sample, Bext, tol_cg, return_last=False, continue_with_least_squares=False, + verbose=True): # read_tmv_from_project("area.bin", conf) nx = sample.nx # area associated with each node @@ -95,7 +96,7 @@ def minimize_gibbs_free_energy(sample, Bext, tol_cg, return_last=False, continue sample.N_iDMI.set_k(0) N_tot = sample.N_dip + sample.N_exc + sample.N_uni + sample.N_DMI + sample.N_iDMI - N_cub = csr_matrix([]) + N_cub = sample.N_cub fac = (sample.Msat * sample.scale) ** 2 * mu_0 # tol_cg = 1e-12 @@ -108,11 +109,18 @@ def minimize_gibbs_free_energy(sample, Bext, tol_cg, return_last=False, continue (np.cos(x[:nx])).mean()), end="") starting_method = "L-BFGS-B" - print(f"Minimizing in using '{starting_method}' (tolerance {tol_cg}) ...") t_start = time.time() - result = minimize(energy_per_length_spherical, m_ini_spherical, (nx, dA, h_ext, N_tot, N_cub), jac=jac_spherical, - callback=print_current_spherical, method=starting_method, - options={"ftol": tol_cg, "gtol": tol_cg}) + if verbose: + print(f"Minimizing in using '{starting_method}' (tolerance {tol_cg}) ...") + + result = minimize(energy_per_length_spherical, m_ini_spherical, (nx, dA, h_ext, N_tot, N_cub), + jac=jac_spherical, + callback=print_current_spherical, method=starting_method, + options={"ftol": tol_cg, "gtol": tol_cg}) + else: + result = minimize(energy_per_length_spherical, m_ini_spherical, (nx, dA, h_ext, N_tot, N_cub), + jac=jac_spherical, method=starting_method, + options={"ftol": tol_cg, "gtol": tol_cg}) t_end = time.time() if result.success: theta = result.x[:nx] @@ -123,13 +131,15 @@ def minimize_gibbs_free_energy(sample, Bext, tol_cg, return_last=False, continue # print(m_final.shape) # print(sample.xyz.shape) - print("\nSuccess!\n") - return m_final + if verbose: + print("\nSuccess!\n") + return m_final, True else: - print("\ndid not work :(\n") - if not continue_least_with_squares: + if not continue_with_least_squares: + print(f"\nRelaxation with {starting_method} method was not succesful.\n") + if not return_last: - return m_ini + return m_ini, False else: theta = result.x[:nx] phi = result.x[-nx:] @@ -137,13 +147,18 @@ def minimize_gibbs_free_energy(sample, Bext, tol_cg, return_last=False, continue # convert to carthesian coordinates m_final = spherical_angles_to_mesh_vector(theta, phi) print("Returning last iteration step.") - return m_final - - if continue_least_with_squares: - print(f"Minimizing in using SLSQP method (tolerance {tol_cg}) ...") - result = minimize(energy_per_length_spherical, result.x, (nx, dA, h_ext, N_tot, N_cub), jac=jac_spherical, - callback=print_current_spherical, method='SLSQP', options={"ftol": tol_cg}) - + return m_final, False + + if continue_with_least_squares: + if verbose: + print(f"\nRelaxation with {starting_method} method was not succesful.\n") + print(f"Minimizing in using SLSQP method (tolerance {tol_cg}) ...") + result = minimize(energy_per_length_spherical, result.x, (nx, dA, h_ext, N_tot, N_cub), + jac=jac_spherical, + callback=print_current_spherical, method='SLSQP', options={"ftol": tol_cg}) + else: + result = minimize(energy_per_length_spherical, result.x, (nx, dA, h_ext, N_tot, N_cub), + jac=jac_spherical, method='SLSQP', options={"ftol": tol_cg}) if result.success: theta = result.x[:nx] phi = result.x[-nx:] @@ -151,14 +166,15 @@ def minimize_gibbs_free_energy(sample, Bext, tol_cg, return_last=False, continue # convert to carthesian coordinates m_final = spherical_angles_to_mesh_vector(theta, phi) - print("\nSuccess!\n") - return m_final + if verbose: + print("\nSuccess!\n") + return m_final, True else: - print("\nDid not work either.\n") + print(f"\nRelaxation with SLSQP method method was not succesful.\n") if return_last: print("\nReturning last iteration step.\n") - return spherical_angles_to_mesh_vector(result.x[:nx], result.x[-nx:]) + return spherical_angles_to_mesh_vector(result.x[:nx], result.x[-nx:]), False else: - return m_ini + return m_ini, False diff --git a/tetrax/geometries.py b/tetrax/geometries.py deleted file mode 100644 index bc51099dc93cb5bd44a39770d004bbbac773a789..0000000000000000000000000000000000000000 --- a/tetrax/geometries.py +++ /dev/null @@ -1,59 +0,0 @@ -import pygmsh - - -def tube_cross_section(r, R, lc=5): - with pygmsh.occ.Geometry() as geom: - geom.characteristic_length_min = lc - geom.characteristic_length_max = lc - disk1 = geom.add_disk([0, 0.0], R) - disk2 = geom.add_disk([0, 0.0], r) - geom.boolean_difference(disk1, disk2) - mesh = geom.generate_mesh() - return mesh - - -def round_wire_cross_section(R, lc=5): - with pygmsh.occ.Geometry() as geom: - geom.characteristic_length_min = lc - geom.characteristic_length_max = lc - disk1 = geom.add_disk([0, 0.0], R) - mesh = geom.generate_mesh() - return mesh - - -def polygonal_cross_section(points, lc=5): - with pygmsh.geo.Geometry() as geom: - geom.add_polygon(points, mesh_size=lc) - mesh = geom.generate_mesh() - return mesh - - -def rectangle_cross_section(a, b, lc_a=5, lc_b=5): - """ - Creates a mesh with rectangular cross section. - - Parameters - ---------- - a : length - b : width - lc_a : characteristic mesh length long a - lc_b : characteristic mesh length long b - - """ - with pygmsh.geo.Geometry() as geom: - nx = int(a / lc_a) - ny = int(b / lc_b) - p = geom.add_point([-a / 2, -b / 2, 0.0], mesh_size=lc_a) - _, l, _ = geom.extrude(p, translation_axis=[a, 0, 0], num_layers=nx) - geom.extrude(l, translation_axis=[0, b, 0], num_layers=ny) - mesh = geom.generate_mesh() - return mesh - - -def rectangle(a, b, lc=5): - with pygmsh.geo.Geometry() as geom: - geom.characteristic_length_min = lc - geom.characteristic_length_max = lc - geom.add_rectangle(0.0, 2.0, 0.0, 1.0, 0.0, 0.1) - mesh = geom.generate_mesh() - return mesh diff --git a/tetrax/geometries/__init__.py b/tetrax/geometries/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..670e57969dc484012234be7ac37130b16f8e48c4 --- /dev/null +++ b/tetrax/geometries/__init__.py @@ -0,0 +1,3 @@ +from .geometries1D import * +from .geometries2D import * +from .geometries3D import * diff --git a/tetrax/geometries/geometries1D.py b/tetrax/geometries/geometries1D.py new file mode 100644 index 0000000000000000000000000000000000000000..a9a2c5b3bb437bff74e283b62c894075e8c15331 --- /dev/null +++ b/tetrax/geometries/geometries1D.py @@ -0,0 +1 @@ +__all__ = [] diff --git a/tetrax/geometries/geometries2D.py b/tetrax/geometries/geometries2D.py new file mode 100644 index 0000000000000000000000000000000000000000..84a32de3139adbb444e60cbe3ed87ec4ac112e14 --- /dev/null +++ b/tetrax/geometries/geometries2D.py @@ -0,0 +1,199 @@ +import numpy as np +import pygmsh + +__all__ = ["tube_cross_section", + "tube_segment_cross_section", + "round_wire_cross_section", + "polygonal_cross_section", + "rectangle_cross_section", + ] + + +def tube_cross_section(r, R, lc=5, x0=0, y0=0): + """Creates a mesh with the cross section of a tube. + + .. image:: /usage/tube_cross_section.png + :width: 350 + + Parameters + ---------- + r : float + Inner radius of the tube. + R : float + Outer radius of the tube. + lc : float + Characteristic length of mesh (default is 5). + x0 : float + x coordinate of the origin (Default is 0). + y0 : float + y coordinate of the origin (Default is 0). + + Returns + ------- + mesh : meshio.Mesh + + See Also + -------- + tube_segment_cross_section, round_wire_cross_section, polygonal_cross_section + """ + with pygmsh.occ.Geometry() as geom: + geom.characteristic_length_min = lc + geom.characteristic_length_max = lc + disk1 = geom.add_disk([x0, y0], R) + disk2 = geom.add_disk([x0, y0], r) + geom.boolean_difference(disk1, disk2) + mesh = geom.generate_mesh() + return mesh + + +def tube_segment_cross_section(arc_length, thickness, angle, lc=5, shifted_to_origin=True): + """Creates a mesh with the cross section of a tube segment. + + .. image:: /usage/tube_segment_cross_section.png + :width: 350 + + Parameters + ---------- + arc_length : float + Average arc length of the tube segment (at the center surface). + thickness : float + Thickness of the tube segment. + angle : float + Opening angle of the tube segment (degrees). + lc : float + Characteristic length of mesh (default is 5). + shifted_to_origin : bool + If True, shift the tube segment to the coordinate origin (default is True). + Otherwise, the origin will be the symmetry axis of the tube. + + Returns + ------- + mesh : meshio.Mesh + + See Also + -------- + tube_cross_section, round_wire_cross_section, polygonal_cross_section + """ + angle = angle * np.pi / 180.0 + if angle == 2 * np.pi: + r_avrg = arc_length / angle + r = r_avrg - thickness / 2 + R = r_avrg + thickness / 2 + return tube_cross_section(r, R, lc, y0=-r_avrg) + elif angle == 0: + return rectangle_cross_section(arc_length, thickness, lc, lc) + else: + r_avrg = arc_length / angle + shift = r_avrg if shifted_to_origin else 0 + r = r_avrg - thickness / 2 + R = r_avrg + thickness / 2 + with pygmsh.occ.Geometry() as geom: + + p0 = geom.add_point([-r * np.sin(-angle / 2), r * np.cos(-angle / 2) - shift], lc) + p1 = geom.add_point([-R * np.sin(-angle / 2), R * np.cos(-angle / 2) - shift], lc) + poly = geom.add_line( + p0, + p1, + + ) + geom.revolve(poly, [0.0, 0.0, 1.0], [0.0, -shift, 0.0], angle) + mesh = geom.generate_mesh() + return mesh + + +def round_wire_cross_section(R, lc=5, x0=0, y0=0): + """Creates a mesh with the cross section of a round wire. + + .. image:: /usage/round_wire_cross_section.png + :width: 350 + + Parameters + ---------- + R : float + Radius of the round wire. + lc : float + Characteristic length of mesh (default is 5). + x0 : float + x coordinate of the origin (Default is 0). + y0 : float + y coordinate of the origin (Default is 0). + + Returns + ------- + mesh : meshio.Mesh + + See Also + -------- + polygonal_cross_section, rectangle_cross_section + """ + with pygmsh.occ.Geometry() as geom: + geom.characteristic_length_min = lc + geom.characteristic_length_max = lc + geom.add_disk([x0, y0], R) + mesh = geom.generate_mesh() + return mesh + + +def polygonal_cross_section(points, lc=5): + """Creates a mesh with the cross section of a polygonal wire. + + .. image:: /usage/polygonal_cross_section.png + :width: 350 + + Parameters + ---------- + points : list (Point) + List of points defining the corners of the polygon. + lc : float + Characteristic length of mesh (default is 5). + + Returns + ------- + mesh : meshio.Mesh + + See Also + -------- + round_wire_cross_section, rectangle_cross_section + """ + with pygmsh.geo.Geometry() as geom: + geom.add_polygon(points, mesh_size=lc) + mesh = geom.generate_mesh() + return mesh + + +def rectangle_cross_section(a, b, lca=5, lcb=5): + """Creates a regular mesh with rectangular cross section. + + .. image:: /usage/rectangle_cross_section.png + :width: 350 + + + Parameters + ---------- + a : float + Width of the rectangle. + b : float + Thickness of the rectangle. + lca : float + characteristic mesh length along the width (Default is 5). + The number of FEM layers along the width will be `a/lca + 1`. + lcb : float + characteristic mesh length along the thickness (Default is 5). + The number of FEM layers along the thickness will be `b/lcb + 1`. + + Returns + ------- + mesh : meshio.Mesh + + See Also + -------- + round_wire_cross_section, polygonal_cross_section + """ + with pygmsh.geo.Geometry() as geom: + nx = int(a / lca) + ny = int(b / lcb) + p = geom.add_point([-a / 2, -b / 2, 0.0], mesh_size=lca) + _, l, _ = geom.extrude(p, translation_axis=[a, 0, 0], num_layers=nx) + geom.extrude(l, translation_axis=[0, b, 0], num_layers=ny) + mesh = geom.generate_mesh() + return mesh diff --git a/tetrax/geometries/geometries3D.py b/tetrax/geometries/geometries3D.py new file mode 100644 index 0000000000000000000000000000000000000000..a9a2c5b3bb437bff74e283b62c894075e8c15331 --- /dev/null +++ b/tetrax/geometries/geometries3D.py @@ -0,0 +1 @@ +__all__ = [] diff --git a/tetrax/helpers/io.py b/tetrax/helpers/io.py index 8d3dd6ad578dbf675b32957cdb5bcd075cf64ed5..d6ea6c3c1851c718cf257524cab7f02d323a3a53 100644 --- a/tetrax/helpers/io.py +++ b/tetrax/helpers/io.py @@ -3,8 +3,10 @@ This submodule provides some helper functions for input and output of files, as well as some mesh checkers. """ -import numpy as np import meshio +import numpy as np + +from ..core.mesh import MeshVector # from meshio topological_dimension = { @@ -94,7 +96,7 @@ def read_mode_from_vtk(fname, as_flattened=False): Returns ------- - numpy.array + MeshVector or FlattenedMeshVector Mode profile as mesh vector of shape `(N, 3)` if `as_flattened=False` or as flattened mesh vector of shape `(3*N,)` if `as_flattened=True`. @@ -110,6 +112,51 @@ def read_mode_from_vtk(fname, as_flattened=False): return np.array([mx, my, mz]).flatten() if as_flattened else np.array([mx, my, mz]).T +def write_field_to_file(field, sample, fname="field.vtk", qname=None): + """ + Writes a scalar or vector field (MeshScalar or MeshVector) to a file using meshio. + + Parameters + ---------- + field : MeshScalar, MeshVector or array_like + Input scalar- or vector field to be saved to the file. If input is not MeshScalar or MeshVector, + a shape check will be performed to infer the data type. + sample : AbstractSample + The sample on which the scalar- or vector field is defined. Necessary to save mesh with vtk. + fname : str + Name of the file, has to contain a file ending from which the format can be + inferred (Default is `"field.vtk"`) + qname + Name of the scalar or vector quantity to appear in the file (default is `None`). If `qname` is `None`, then + the name will be set to `scalar` or `vector`, depending on the input field. + """ + + if field.shape == (sample.nx,): + # scalar field case + qname = "scalar" if qname is None else qname + meshio.write_points_cells(filename=fname, + points=sample.xyz, + cells=sample.mesh.cells, + point_data={qname: field} + ) + + elif field.shape == (sample.nx, 3): + # vector field case + + if not isinstance(field, MeshVector): + field = MeshVector(field) + + qname = "vector" if qname is None else qname + meshio.write_points_cells(filename=fname, + points=sample.xyz, + cells=sample.mesh.cells, + point_data={qname: field} + ) + + else: + print("Malshaped input field. Only fields with shape of appropriate MeshScalar or MeshVector can be saved.") + + def get_mesh_dimension(mesh: meshio.Mesh) -> int: """Obtain the dimension of a mesh. @@ -135,9 +182,9 @@ def get_mesh_dimension(mesh: meshio.Mesh) -> int: def check_mesh_shape(mesh: meshio.Mesh, dim: int) -> bool: """Check if mesh is properly shaped. - - `dim=3`: always `True` - - `dim=2`: `True` if mesh is in `xy` plane. - - `dim=1`: `True` if mesh is on `y` axis. + - ``dim == 3``: always `True` + - ``dim == 2``: `True` if mesh is in `xy` plane. + - ``dim == 1``: `True` if mesh is on `y` axis. Parameters ---------- diff --git a/tetrax/helpers/math.py b/tetrax/helpers/math.py index 31eb071b391744a473b6680b3e7ed005eb8ed38e..48e9163ed5642636bc61bb2374378d4afc9d7c98 100644 --- a/tetrax/helpers/math.py +++ b/tetrax/helpers/math.py @@ -8,14 +8,30 @@ is used. import numpy as np from scipy.sparse import dia_matrix, bmat +from ..core.mesh import * + + def normalize_single_3d_vec(vec): - norm = np.sqrt(vec.T[0]**2 + vec.T[1]**2 + vec.T[2]**2) - norm = np.repeat(norm,3) - norm = np.reshape(norm,(len(norm)//3,3)) - return np.array(vec)/norm + """Normalizes a vector field. + + Parameters + ---------- + vec : MeshVector + Mesh vector of shape `(N,3)`. + + Returns + ------- + vec_normalized : MeshVector + Normalized mesh vector of shape `(N,3)`. + """ + norm = np.sqrt(vec.T[0] ** 2 + vec.T[1] ** 2 + vec.T[2] ** 2) + norm = np.repeat(norm, 3) + norm = np.reshape(norm, (len(norm) // 3, 3)) + return np.array(vec) / norm + def flattened_mesh_vec_abs(vec): - """Calculates the node-wise magnitude of a flattened mesh vector :math:`\mathbf{v}`. + """Calculates the node-wise magnitude of a :class:`FlattenedMeshVector` :math:`\mathbf{v}`. Assumes that the mesh vector is of shape `(3*N,)` (with `N` being the number of nodes in the mesh) and is ordered according to @@ -28,13 +44,13 @@ def flattened_mesh_vec_abs(vec): Parameters ---------- - vec : np.array + vec : FlattenedMeshVector Flattened mesh vector of shape `(3*N,)`. Returns ------- - np.array - Flattened mesh scalar of shape `(N,)`. + MeshScalar + Mesh scalar of shape `(N,)`. """ nx = vec.shape[0] // 3 return np.sqrt(vec[:nx] ** 2 + vec[nx:2 * nx] ** 2 + vec[2 * nx:] ** 2) @@ -57,15 +73,15 @@ def flattened_mesh_vec_scalar_product(vec1, vec2): Parameters ---------- - vec1 : np.array + vec1 : FlattenedMeshVector Flattened mesh vector of shape `(3*N,)`. - vec2 : np.array + vec2 : FlattenedMeshVector Flattened mesh vector of shape `(3*N,)`. Returns ------- - np.array + MeshScalar Flattened mesh scalar of shape `(N,)`. """ @@ -110,16 +126,16 @@ def flattened_mesh_vec_tensor_product(vec1, vec2): Parameters ---------- - vec1 : np.array + vec1 : FlattenedMeshVector Flattened mesh vector of shape `(3*N,)`. - vec2 : np.array + vec2 : FlattenedMeshVector Flattened mesh vector of shape `(3*N,)`. Returns ------- scipy.sparse.csr_matrix - Sparse matrix of shape `(3*N, 3*N)` in `csr` format. + Sparse matrix of shape ``(3*N, 3*N)`` in ``csr`` format. """ nx = vec1.shape[0] // 3 @@ -173,15 +189,15 @@ def flattened_mesh_vec_scalar_product2d(vec1, vec2): Parameters ---------- - vec1 : np.array - Flattened mesh vector of shape `(2*N,)`. + vec1 : FlattenedLocalMeshVector + Flattened local mesh vector of shape `(2*N,)`. - vec2 : np.array - Flattened mesh vector of shape `(2*N,)`. + vec2 : FlattenedLocalMeshVector + Flattened local mesh vector of shape `(2*N,)`. Returns ------- - np.array + MeshScalar Flattened mesh scalar of shape `(N,)`. """ @@ -190,6 +206,41 @@ def flattened_mesh_vec_scalar_product2d(vec1, vec2): return vec1[:nx] * vec2[:nx] + vec1[nx:2 * nx] * vec2[nx:2 * nx] +def sample_average(field, sample): + """Calculates the sample average of a scalar or vector field. + + Parameters + ---------- + field : MeshScalar or MeshVector + sample : AbstractSample + Sample on which the field is define. Required to obtain the (generalized) volume elments. + + Returns + ------- + averaged_field : float or numpy.array + Returns averaged scalar fields as a ``float`` and averaged vector fields as a triplet of ``float``s. + """ + + total_volume = np.sum(sample.dA) + + if field.shape == (sample.nx,): + # scalar field case + averaged_field = np.sum(field * sample.dA) / total_volume + return averaged_field + + elif field.shape == (sample.nx, 3): + # vector field case + + if not isinstance(field, MeshVector): + field = MeshVector(field) + + averaged_field_x = np.sum(field.x * sample.dA) / total_volume + averaged_field_y = np.sum(field.y * sample.dA) / total_volume + averaged_field_z = np.sum(field.z * sample.dA) / total_volume + + return np.array([averaged_field_x, averaged_field_y, averaged_field_z]) + + def spherical_angles_to_mesh_vector(theta, phi, as_flattened=False): """Node-wise, convert spherical angles to a unit vector. @@ -197,16 +248,16 @@ def spherical_angles_to_mesh_vector(theta, phi, as_flattened=False): Parameters ---------- - theta : np.array + theta : MeshScalar Polar angle at each node point. - phi : np.array + phi : MeshScalar Azimuthal angle at each node point. as_flattened : bool, optional Return resulting unit vector as flattened mesh vector (default is `False`). Returns ------- - np.array + MeshVector or FlattenedMeshVector Unit mesh vector of shape `(N, 3)` if `as_flattened=False` or as flattened mesh vector of shape `(3*N,)` if `as_flattened=True`. """ @@ -215,7 +266,8 @@ def spherical_angles_to_mesh_vector(theta, phi, as_flattened=False): vec_y = np.sin(theta) * np.sin(phi) vec_z = np.cos(theta) - return np.concatenate((vec_x, vec_y, vec_z)).flatten() if as_flattened else np.concatenate((vec_x, vec_y, vec_z)).flatten().reshape(3, nx).T + vec_flattened = FlattenedMeshVector(np.concatenate((vec_x, vec_y, vec_z)).flatten()) + return vec_flattened if as_flattened else vec_flattened.to_unflattened() def diag_matrix_from_vec(v): @@ -302,7 +354,7 @@ def h_CPW(k,A,L,g,s): References ---------- - .. [1] M. Sushruth, *et al.*, "Electrical spectroscopy of forward volume spin + .. [1] M. Sushruth, *et al.*, "Electrical spectroscopy of forward volume spin waves in perpendicularly magnetized materials", `Physical Review Research 2, 043203 (2020) <https://journals.aps.org/prresearch/abstract/10.1103/PhysRevResearch.2.043203>`_. """ @@ -379,5 +431,32 @@ def h_hom(k, A): with :math:`\delta` being the Dirac distribution. """ - return A*np.piecewise(k, [k == 0, k != 0], [1, 0]) + return A * np.piecewise(k, [k == 0, k != 0], [1, 0]) + + +def matrix_elem(i, j, A, dV): + """ + Calculates the element of an operator `A` in the basis with respect to the basis vectors `i` and `j` as + + .. math:: + A_{i,j} = \int\mathrm{d}V \ \mathbf{i}^*\cdot \hat{\mathbf{A}} \cdot \mathbf{j}. + + Parameters + ---------- + i : FlattenedMeshVector + Flattened mesh vector of shape `(3*N,)`. + j : FlattenedMeshVector + Flattened mesh vector of shape `(3*N,)`. + A : scipy.sparse.linalg.LinearOperator + LinearOperator of shape `(3*N, 3*N)`. + dV : MeshScalar + Volume elements of the mesh. + + Returns + ------- + float + """ + rhs = A.dot(j) + + temp = flattened_mesh_vec_scalar_product2d(np.conjugate(i), rhs) diff --git a/tetrax/vectorfields.py b/tetrax/vectorfields.py index e733087126b3fd06ab349f2f95ebe5e767a1b9fe..91e46f7fa4627e88d6dea80a31e3bf552f21ed80 100644 --- a/tetrax/vectorfields.py +++ b/tetrax/vectorfields.py @@ -2,6 +2,36 @@ import numpy as np def helical(xyz, Theta, chi): + """Helical vector field with given angle :math:`\Theta` and circularity :math:`\chi`. + + The field is calculated according to + + .. math:: \mathbf{v} = \chi\sin(\Theta) \mathbf{e}_\phi + \cos(\Theta)\mathbf{e}_z + + which, in cartesian basis, is + + .. math:: \mathbf{v} = -\chi\sin(\Theta)\sin(\phi) \mathbf{e}_x + \chi\sin(\Theta)\cos(\phi) \mathbf{e}_y + \cos(\Theta)\mathbf{e}_z + + with :math:`\phi` being the azimuthal angle in a cylindrical coordinate system. + + Parameters + ---------- + xyz : MeshVector + Coordinates on a mesh. + Theta : float + Angle :math:`\Theta` of the helical field with the :math:`z` axis (given in `degree`). + chi : int + Circularity :math:`\chi` or sense of rotation around the :math:`z` axis (either ``1`` or ``-1``). + + Returns + ------- + MeshVector + Vector field :math:`\mathbf{v}` defined at each node of the mesh. + + See Also + -------- + radial, circular_vortex, radial_vortex + """ Theta = Theta / 180 * np.pi x = xyz.T[0] y = xyz.T[1] @@ -12,7 +42,143 @@ def helical(xyz, Theta, chi): return np.array([mx, my, mz]).T +def bloch_wall(xyz, thickness=10, x0=0, domain_axis="y", domain_polarity=1, wall_polarity=1): + r"""Domain wall of Bloch type perpendicular to the :math:`x` direction. + + The field is calculated according to + + .. math:: \mathbf{v} = \begin{cases} p_\mathrm{D} \cos(\theta)\mathbf{e}_y + p_\mathrm{DW} \sin(\theta)\mathbf{e}_z, & \text{domains polarized along } y\\ + p_\mathrm{DW} \sin(\theta) \mathbf{e}_y + p_\mathrm{D} \cos(\theta)\mathbf{e}_z, & \text{domains polarized along } z + \end{cases} + + with :math:`p_\mathrm{D}` and :math:`p_\mathrm{DW}` being the ``domain_polarity`` and the ``wall_polarity``, + respectively. The domain-wall angle :math:`\theta = \theta(x)` is given by the Bloch wall solution + + .. math:: \theta(x) = 2 \arctan\left(e^{(x-x_0)/\Delta}\right) + + with :math:`\Delta` being the ``thickness`` and :math:`x_0 =` ``x0`` being the position of the wall. + + Parameters + ---------- + xyz : MeshVector + Coordinates on a mesh. + thickness : float + Thickness of the domain wall (default is 10). + x0 : float + Position of the domain wall along the :math:`x` direction (default is 0). + domain_axis : {"y", "z"} + Axis, along which the domains adjecent to the Bloch wall are oriented (default is "y"). + domain_polarity : {1, -1} + Polarity of the `first` domain with respect to the ``domain_axis``. + wall_polarity : {1, -1} + Polarity of the domain wall. + + Returns + ------- + MeshVector + Vector field :math:`\mathbf{v}` defined at each node of the mesh. + + See Also + -------- + neel_wall + """ + x = xyz.x + y = xyz.y + theta = 2 * np.arctan(np.exp((x - x0) / thickness)) + mx = np.zeros_like(x) + if domain_axis == "y": + my = domain_polarity * np.cos(theta) + mz = wall_polarity * np.sin(theta) + elif domain_axis == "z": + mz = domain_polarity * np.cos(theta) + my = wall_polarity * np.sin(theta) + else: + raise ValueError("No valid domain axis has been supplied.") + + return np.array([mx, my, mz]).T + + +def neel_wall(xyz, thickness=10, x0=0, domain_axis="z", domain_polarity=1, wall_polarity=1): + r"""Domain wall of Neel type perpendicular to the :math:`x` direction. + + This profile is simply being approximated as the profile of a Bloch wall (see :func:`bloch_wall`), however, + with rotation of the vecto field perpendicular to the wall plane. + + Parameters + ---------- + xyz : MeshVector + Coordinates on a mesh. + thickness : float + Thickness of the domain wall (default is 10). + x0 : float + Position of the domain wall along the :math:`x` direction (default is 0). + domain_axis : {"y", "z"} + Axis, along which the domains adjecent to the Bloch wall are oriented (default is "z"). + domain_polarity : {1, -1} + Polarity of the `first` domain with respect to the ``domain_axis``. + wall_polarity : {1, -1} + Polarity of the domain wall. + + Returns + ------- + MeshVector + Vector field :math:`\mathbf{v}` defined at each node of the mesh. + + See Also + -------- + bloch_wall + """ + x = xyz.x + y = xyz.y + theta = 2 * np.arctan(np.exp((x - x0) / thickness)) + my = np.zeros_like(x) + + if domain_axis == "y": + mz = wall_polarity * np.sin(theta) + mx = domain_polarity * np.cos(theta) + elif domain_axis == "z": + mx = wall_polarity * np.sin(theta) + mz = domain_polarity * np.cos(theta) + else: + raise ValueError("No valid domain axis has been supplied.") + + return np.array([mx, my, mz]).T + + def radial(xyz, p): + r"""Radial vector field with given polarization :math:`p`. + + The field is calculated according to + + .. math:: \mathbf{v} = p \mathbf{e}_\rho. + + + Parameters + ---------- + xyz : MeshVector + Coordinates on a mesh. + p : int + Polarization :math:`p` of the radial field (either ``1`` for outward or ``-1`` for inward). + + Returns + ------- + MeshVector + Vector field :math:`\mathbf{v}` defined at each node of the mesh. + + See Also + -------- + helical, circular_vortex, radial_vortex + """ + x = xyz.x + y = xyz.y + mx = p * x / np.sqrt(x ** 2 + y ** 2) + my = p * y / np.sqrt(x ** 2 + y ** 2) + mz = np.zeros_like(y) + return np.array([mx, my, mz]).T + + +def radial_vortex(xyz, p): + """Not implemented yet.""" x = xyz.T[0] y = xyz.T[1] mx = p * x / np.sqrt(x ** 2 + y ** 2) @@ -22,19 +188,32 @@ def radial(xyz, p): def homogeneous(xyz, theta, phi): - """ - Initializes homogeneous magnetization state. + """Homogeneous magnetization state with direction given by spherical angles :math:`\\theta` and :math:`\phi`. Parameters - __________ + ---------- + xyz : MeshVector + Coordinates on a mesh. + theta : float + Polar angle :math:`\\theta` given in `degree`. + phi : float + Azimuthal angle :math:`\phi` given in `degree`. + + Returns + ------- + MeshVector + Vector field :math:`\mathbf{v}` defined at each node of the mesh. - xyz : coordinates of the nodes (sample vector) - theta, phi : the required direction in spherical coordinates + Notes + ----- + Setting a homogeneous magnetization on sample ``sample`` or in an experimental setup ``exp``, the same can also be achieved + by giving triplets. - Return - ------ - mag : Normalized magnetization in sample vector format. + >>> sample.mag = [0, 0, 1] + or + + >>> exp.Bext = [0.1, 0, 0] """ theta = theta / 180.0 * np.pi phi = phi / 180.0 * np.pi @@ -44,71 +223,108 @@ def homogeneous(xyz, theta, phi): return np.array([mx, my, mz], like=xyz) -def vortex(xyz, chi, p): - """ - vortex state - """ - x = xyz.T[0] - y = xyz.T[1] +def circular_vortex(xyz, chi, p, rho_c=10): + r"""Vortex state with given circularity :math:`\chi` and core polarity :math:`p`. - rho_c = 10 - rho = np.sqrt(x ** 2 + y ** 2) + The field is calculated according to - # Usov Pechany ansatz - theta = np.pi / 2 * np.ones_like(rho) - theta[rho <= rho_c] = 2 * np.arctan(rho / rho_c) + .. math:: \mathbf{v} = \chi\sin[\theta(\rho)] \mathbf{e}_\phi +\cos[\theta(\rho)]\mathbf{e}_z - mx = chi * np.sin(theta) * (-np.sin(theta)) - my = chi * np.sin(theta) * (np.cos(theta)) - mz = p * np.cos(theta) * np.ones_like(x) - return np.array([mx, my, mz]).T + which, in cartesian basis, is + .. math:: \mathbf{v} = -\chi\sin[\theta(\rho)]\sin(\phi) \mathbf{e}_x + \chi\sin[\theta(\rho)]\cos(\phi) \mathbf{e}_y + \cos[\theta(\rho)]\mathbf{e}_z -def onion_tube_axial(xyz, pol_y=1, pol_n=1, pol_s=1, delta=5): - """ - Onion state with domain wall along y at x = 0. The domain walls rotating on a cylinder (not in radial direction). + with :math:`\phi` being the azimuthal angle and :math:`\rho` being the radius in a cylindrical coordinate system. + Here, :math:`\theta(\rho)` is the angle of the vector field with the :math:`z` axis and is approximated by + the Usov-Pechany ansatz - pol_y : polarization of state in y direction (1 or -1) - delta : thickness of domain walls (default is 5) - pol_n : polarity (z) of domain wall in y > 0 region - pol_s : polarity (z) of domain wall in y < 0 region - """ + .. math:: \theta(\rho) = \begin{cases} 2\arctan(\rho/\rho_c), & \rho \leq \rho_c \\ + \pi/2, & \rho > \rho_c\end{cases} - x = xyz.T[0] - y = xyz.T[1] + Parameters + ---------- + xyz : MeshVector + Coordinates on a mesh. + chi : int + Circularity :math:`\chi` or sense of rotation around the :math:`z` axis (either ``1`` or ``-1``). + p : int + Polarity :math:`p` of the vortex core along the :math:`z` axis (either ``1`` or ``-1``). - phi = np.arctan2(y, x) - mx = np.zeros_like(x) - my = np.zeros_like(x) - # theta = np.ones_like(x)*np.pi/2 + Returns + ------- + MeshVector + Vector field :math:`\mathbf{v}` defined at each node of the mesh. - theta = 2 * np.arctan(np.exp(x / delta)) - np.pi / 2 + See Also + -------- + helical, radial_vortex - mx[x > 0] = pol_y * (-np.sin(phi[x > 0])) * np.sin(theta[x > 0]) - mx[x <= 0] = pol_y * (-np.sin(phi[x <= 0])) * np.sin(theta[x <= 0]) - my[x > 0] = pol_y * (np.cos(phi[x > 0])) * np.sin(theta[x > 0]) - my[x <= 0] = pol_y * (np.cos(phi[x <= 0])) * np.sin(theta[x <= 0]) + References + ---------- + .. [1] N.A. Usov and S.E. Peschany, "Magnetization curling in a fine cylindrical particle", `J. Magn. Magn. Mater. 118, L290 (1993) <https://doi.org/10.1016/0304-8853(93)90428-5>`_. - mz = np.cos(theta) - mz[y > 0] *= pol_n - mz[y <= 0] *= pol_s - return np.array([mx, my, mz]).T + """ + x = xyz.x + y = xyz.y -def neel_tube(xyz, chi): - x = xyz.T[0] - y = xyz.T[1] + rho = np.sqrt(x ** 2 + y ** 2) phi = np.arctan2(y, x) - mx = np.zeros_like(x) - my = np.zeros_like(x) - delta = 5 - # theta = np.ones_like(x)*np.pi/2 - theta = 2 * np.arctan(np.exp(x / delta)) - mx[x > 0] = chi * (-np.sin(phi[x > 0])) * np.sin(theta[x > 0]) - mx[x <= 0] = -chi * (-np.sin(phi[x <= 0])) * np.sin(theta[x <= 0]) - my[x > 0] = chi * (np.cos(phi[x > 0])) * np.sin(theta[x > 0]) - my[x <= 0] = -chi * (np.cos(phi[x <= 0])) * np.sin(theta[x <= 0]) + # Usov Pechany ansatz + theta = np.piecewise(rho, [rho <= rho_c, rho > rho_c], + [lambda rho: 2 * np.arctan(rho / rho_c), lambda rho: np.pi / 2]) - mz = np.cos(theta) + mx = chi * np.sin(theta) * (-np.sin(phi)) + my = chi * np.sin(theta) * (np.cos(phi)) + mz = p * np.cos(theta) * np.ones_like(x) return np.array([mx, my, mz]).T + +# def onion_tube_axial(xyz, pol_y=1, pol_n=1, pol_s=1, delta=5): +# """ +# Onion state with domain wall along y at x = 0. The domain walls rotating on a cylinder (not in radial direction). +# +# pol_y : polarization of state in y direction (1 or -1) +# delta : thickness of domain walls (default is 5) +# pol_n : polarity (z) of domain wall in y > 0 region +# pol_s : polarity (z) of domain wall in y < 0 region +# """ +# +# x = xyz.T[0] +# y = xyz.T[1] +# +# phi = np.arctan2(y, x) +# mx = np.zeros_like(x) +# my = np.zeros_like(x) +# # theta = np.ones_like(x)*np.pi/2 +# +# theta = 2 * np.arctan(np.exp(x / delta)) - np.pi / 2 +# +# mx[x > 0] = pol_y * (-np.sin(phi[x > 0])) * np.sin(theta[x > 0]) +# mx[x <= 0] = pol_y * (-np.sin(phi[x <= 0])) * np.sin(theta[x <= 0]) +# my[x > 0] = pol_y * (np.cos(phi[x > 0])) * np.sin(theta[x > 0]) +# my[x <= 0] = pol_y * (np.cos(phi[x <= 0])) * np.sin(theta[x <= 0]) +# +# mz = np.cos(theta) +# mz[y > 0] *= pol_n +# mz[y <= 0] *= pol_s +# return np.array([mx, my, mz]).T +# +# +# def neel_tube(xyz, chi): +# x = xyz.T[0] +# y = xyz.T[1] +# phi = np.arctan2(y, x) +# mx = np.zeros_like(x) +# my = np.zeros_like(x) +# delta = 5 +# # theta = np.ones_like(x)*np.pi/2 +# theta = 2 * np.arctan(np.exp(x / delta)) +# +# mx[x > 0] = chi * (-np.sin(phi[x > 0])) * np.sin(theta[x > 0]) +# mx[x <= 0] = -chi * (-np.sin(phi[x <= 0])) * np.sin(theta[x <= 0]) +# my[x > 0] = chi * (np.cos(phi[x > 0])) * np.sin(theta[x > 0]) +# my[x <= 0] = -chi * (np.cos(phi[x <= 0])) * np.sin(theta[x <= 0]) +# +# mz = np.cos(theta) +# return np.array([mx, my, mz]).T