├── .gitignore
├── LICENSE
├── NOTICE
├── README.md
├── examples
├── .assets
│ ├── ex1.svg
│ └── ex9.svg
├── ex1.ipynb
├── ex9.ipynb
└── plot.ipynb
├── glvis
├── __about__.py
├── __init__.py
├── util.py
├── widget.js
└── widget.py
└── pyproject.toml
/.gitignore:
--------------------------------------------------------------------------------
1 | .ipynb_checkpoints
2 | glvis/nbextension
3 | js/node_modules
4 | *.saved
5 | *.mesh
6 | __pycache__
7 | *.egg-info
8 | js/dist
9 | build
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2010-2024, Lawrence Livermore National Security, LLC
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | * Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | * Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | * Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | This work was produced under the auspices of the U.S. Department of Energy by
2 | Lawrence Livermore National Laboratory under Contract DE-AC52-07NA27344.
3 |
4 | This work was prepared as an account of work sponsored by an agency of
5 | the United States Government. Neither the United States Government nor
6 | Lawrence Livermore National Security, LLC, nor any of their employees
7 | makes any warranty, expressed or implied, or assumes any legal liability
8 | or responsibility for the accuracy, completeness, or usefulness of any
9 | information, apparatus, product, or process disclosed, or represents that
10 | its use would not infringe privately owned rights.
11 |
12 | Reference herein to any specific commercial product, process, or service
13 | by trade name, trademark, manufacturer, or otherwise does not necessarily
14 | constitute or imply its endorsement, recommendation, or favoring by the
15 | United States Government or Lawrence Livermore National Security, LLC.
16 |
17 | The views and opinions of authors expressed herein do not necessarily
18 | state or reflect those of the United States Government or Lawrence
19 | Livermore National Security, LLC, and shall not be used for advertising
20 | or product endorsement purposes.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PyGLVis
2 |
3 | [](https://colab.research.google.com/github/GLVis/pyglvis/blob/main/examples/ex1.ipynb)
4 | [](https://colab.research.google.com/github/GLVis/pyglvis/blob/main/examples/ex9.ipynb)
5 |
6 |
7 | \
8 | PyGLVis is an interactive [Jupyter](https://jupyter.org/) widget for visualizing finite element meshes and functions, built on-top of the [GLVis](https://glvis.org/) library.
9 |
10 | ## 📦 Installation
11 |
12 | The GLVis Jupyter widget is installed using `pip`. To install the latest version from the repository:
13 |
14 | ```bash
15 | git clone https://github.com/GLVis/pyglvis.git
16 | cd pyglvis
17 | pip install .
18 | ```
19 |
20 | Or, install directly from PyPi,
21 | ```bash
22 | pip install glvis
23 | ```
24 |
25 | PyGLVis requires the Python wrapper for MFEM, [PyMFEM](https://github.com/mfem/pymfem), which can be installed with
26 | ```bash
27 | pip install mfem
28 | ```
29 |
30 |
31 |
32 | ## 🚀 Usage
33 |
34 | ### Basic usage
35 |
36 | ```python
37 | from glvis import glvis
38 |
39 | # Create a `glvis` object
40 | g = glvis(data, width=640, height=480)
41 |
42 | # Run a cell with `g` as the last statement to display the widget
43 | g
44 | ```
45 |
46 | The `data` object can be one of:
47 |
48 | - `Mesh`, defined in [PyMFEM](https://github.com/mfem/pymfem)
49 | - `(Mesh, GridFunction)` tuple, defined in [PyMFEM](https://github.com/mfem/pymfem)
50 | - `str`, in the format of `*.saved` files [used by MFEM and GLVis](https://mfem.org/mesh-format-v1.0/). See [examples/basic.ipynb](examples/basic.ipynb) for an example.
51 |
52 | ### Customization with key commands
53 |
54 | GLVis has many keyboard commands that can be used to customize the visualization.
55 | A few of the most common are listed below. See the [GLVis README](https://github.com/GLVis/glvis?tab=readme-ov-file#key-commands) for a full list.
56 | - `r` - reset the view
57 | - `c` - toggle the colorbar
58 | - `j` - toggle perspective
59 | - `l` - toggle the light
60 | - `g` - toggle the background color (white/black)
61 | - `a` - cycle through bounding box axes states
62 | - `m` - cycle through mesh states
63 | - `p` - cycle through color palettes
64 | - `t` - cycle through materials and lights
65 | - `0` - begin rotating around z-axis
66 | - `.` - pause rotation
67 | - `*`/`/` - zoom in/out
68 |
69 | These can be set using the `keys` argument when creating a `glvis` object.
70 | ```python
71 | glvis(data, keys='rljgac//0')
72 | ```
73 | This combination of keys would: `r` reset the view, `l` toggle the light, `j` toggle perspective, `g` toggle the background color to black (default is white), `a` show the bounding box, `c` show the colorbar, `//` zoom out twice, and `0` begin rotating around the z-axis:
74 |
75 | 
76 |
77 | Alternatively, keys can be typed directly into the widget after it has been created:
78 |
79 | 
80 |
81 | ### Other methods
82 |
83 | Once you have a `glvis` object there are a few methods that can used to update the
84 | visualization, besides using keys:
85 | ```python
86 | # Show a new Mesh/GridFunction, resets keys
87 | g.plot(data)
88 | # Show an updated visualization with the same data, preserving keys
89 | g.update(data)
90 | # Change the image size
91 | g.set_size(width, height)
92 | # Force the widget to render. If the widget isn't the last statement in a cell it
93 | # will not be shown without this. See ex9.ipynb
94 | g.render()
95 | ```
96 |
97 | See the [examples](examples/) directory for additional examples. To test those locally, start a Jupyter lab server with
98 |
99 | ```
100 | jupyter lab
101 | ```
102 |
103 | ## 🐛 Troubleshooting
104 |
105 | This widget was originally developed using the [jupyter widget cookiecutter](https://github.com/jupyter-widgets/widget-cookiecutter); however, [recent changes to the Jupyter ecosystem](https://jupyter-notebook.readthedocs.io/en/latest/migrate_to_notebook7.html#why-a-new-version) have broken a lot of functionality, leading to a rewrite using [anywidget](https://anywidget.dev/). If you encounter any problems, please consider supporting development by opening an [issue](https://github.com/GLVis/pyglvis/issues).
106 |
107 |
108 |
109 | ## 🤖 Development
110 |
111 | ### PyGLVis dependencies
112 |
113 | ```mermaid
114 | graph TD;
115 | A[mfem] --> B[pymfem];
116 | A --> C[glvis];
117 | C --> D[glvis-js];
118 | Ext1[emscripten] --> D;
119 | D-.-E["glvis-js (esm)"]
120 | B & E --> G[pyglvis];
121 | Ext2[jupyter] --> G;
122 | ```
123 |
124 | `pyglvis` is most directly dependent on `PyMFEM` and `glvis-js`. [PyMFEM](https://github.com/mfem/pymfem) is a Python wrapper of the finite element library, `MFEM`, while `glvis-js` is a JavaScript/WebAssembly port of `glvis`.
125 |
126 | `glvis-js` is hosted on [github](https://github.com/glvis/glvis-js) and mirrored on [npm](https://www.npmjs.com/package/glvis). [esm.sh](https://esm.sh/glvis) allows `pyglvis` to pull the latest version of `glvis-js` directly from npm. This can be seen in the first line of [glvis/widget.js](glvis/widget.js):
127 |
128 | ```
129 | import glvis from "https://esm.sh/glvis";
130 | ```
131 |
132 | You can specify a different version of `glvis-js` by adding `@x.y.z` to the end of this import statement, where `x.y.z` matches a version number available on `npm`, e.g.
133 |
134 | ```
135 | import glvis from "https://esm.sh/glvis@0.6.3";
136 | ```
137 |
138 |
139 | ### Releasing a new version of glvis on NPM:
140 |
141 | To publish a new version of `glvis-js`, follow the instructions on the [repo](https://github.com/GLVis/glvis-js/tree/master).
142 |
143 |
144 | ### Releasing a new version of glvis on PyPI:
145 |
146 | - Update `__version__` in `glvis/__about__.py`
147 |
148 | - `git add` and `git commit` changes
149 |
150 |
151 | You will need [twine](https://pypi.org/project/twine/) to publish to PyPI, install with `pip`.
152 |
153 | ```
154 | python -m hatch build
155 | twine upload dist/*
156 | git tag -a X.X.X -m 'comment'
157 | git push --tags
158 | ```
159 |
160 |
161 | ## 🌐 Links
162 | - MFEM ([website](https://mfem.org/), [github](https://github.com/mfem/mfem))
163 | - PyMFEM ([github](https://github.com/mfem/pymfem), [pypi](https://pypi.org/project/mfem/))
164 | - GLVis ([website](https://glvis.org/), [github](https://github.com/glvis/glvis))
165 | - glvis-js ([github](https://github.com/glvis/glvis-js), [npm](https://www.npmjs.com/package/glvis), [esm](https://esm.sh/glvis))
166 | - pyglvis ([github](https://github.com/GLVis/pyglvis), [pypi]())
167 |
--------------------------------------------------------------------------------
/examples/.assets/ex1.svg:
--------------------------------------------------------------------------------
1 |
2 |
129 |
--------------------------------------------------------------------------------
/examples/.assets/ex9.svg:
--------------------------------------------------------------------------------
1 |
2 | Open in ColabOpen in Colabex9
163 |
--------------------------------------------------------------------------------
/examples/ex1.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "## Imports"
8 | ]
9 | },
10 | {
11 | "cell_type": "code",
12 | "execution_count": null,
13 | "metadata": {},
14 | "outputs": [],
15 | "source": [
16 | "# Pip installs pyglvis and pymfem if in a colab notebook. Can comment out if not required\n",
17 | "# PyMFEM requires numpy < 2.0.0 \n",
18 | "import importlib.metadata, os\n",
19 | "\n",
20 | "desired_numpy = \"1.26.4\"\n",
21 | "\n",
22 | "installed_numpy = importlib.metadata.version(\"numpy\")\n",
23 | "if installed_numpy != desired_numpy:\n",
24 | " print(f\"Installing NumPy {desired_numpy} (current: {installed_numpy})... \")\n",
25 | " print(\"Runtime will restart... Run again for changes to take effect.\")\n",
26 | " %pip install numpy=={desired_numpy} --prefer-binary --quiet\n",
27 | " os._exit(0)\n",
28 | "print(f\"Correct NumPy ({desired_numpy}) is installed\")\n",
29 | "\n",
30 | "%pip install --quiet glvis\n",
31 | "from glvis import glvis, GlvisData\n"
32 | ]
33 | },
34 | {
35 | "cell_type": "markdown",
36 | "metadata": {},
37 | "source": [
38 | "## Visualizing from stream"
39 | ]
40 | },
41 | {
42 | "cell_type": "code",
43 | "execution_count": null,
44 | "metadata": {},
45 | "outputs": [],
46 | "source": [
47 | "# Load a remote data stream\n",
48 | "stream = GlvisData.ex9\n",
49 | "# Visualize the above stream (all GLVis keys and mouse commands work)\n",
50 | "glvis(stream, 300, 300)"
51 | ]
52 | },
53 | {
54 | "cell_type": "code",
55 | "execution_count": null,
56 | "metadata": {},
57 | "outputs": [],
58 | "source": [
59 | "# Another visualization instance of the same stream\n",
60 | "glvis(stream, 300, 300, keys='rljg****ttta0')"
61 | ]
62 | },
63 | {
64 | "cell_type": "code",
65 | "execution_count": null,
66 | "metadata": {},
67 | "outputs": [],
68 | "source": [
69 | "# Load in another stream (press Q to cycle between quadrature representations)\n",
70 | "glvis(GlvisData.quadrature_lor, keys=\"gcQ/////////\")"
71 | ]
72 | },
73 | {
74 | "cell_type": "markdown",
75 | "metadata": {},
76 | "source": [
77 | "## Solving and visualizing the Laplace equation"
78 | ]
79 | },
80 | {
81 | "cell_type": "markdown",
82 | "metadata": {
83 | "tags": []
84 | },
85 | "source": [
86 | "### MFEM Example 1\n",
87 | "\n",
88 | "Adapted from [PyMFEM/ex1.py](https://github.com/mfem/PyMFEM/blob/master/examples/ex1.py).\n",
89 | "Compare with the [original Example 1](https://github.com/mfem/mfem/blob/master/examples/ex1.cpp) in MFEM.\n",
90 | "\n",
91 | "This example code demonstrates the use of MFEM to define a simple finite element discretization of the Laplace problem\n",
92 | "\n",
93 | "\\begin{equation*}\n",
94 | "-\\Delta x = 1\n",
95 | "\\end{equation*}\n",
96 | "\n",
97 | "in a domain $\\Omega$ with homogeneous Dirichlet boundary conditions\n",
98 | "\n",
99 | "\\begin{equation*}\n",
100 | "x = 0\n",
101 | "\\end{equation*}\n",
102 | "\n",
103 | "on the boundary $\\partial \\Omega$.\n",
104 | "\n",
105 | "The problem is discretized on a computational mesh in either 2D or 3D using a finite elements space of the specified order (2 by default) resulting in the global sparse linear system\n",
106 | "\n",
107 | "\\begin{equation*}\n",
108 | "A X = B.\n",
109 | "\\end{equation*}\n",
110 | "\n",
111 | "The example highlights the use of mesh refinement, finite element grid functions, as well as linear and bilinear forms corresponding to the left-hand side and right-hand side of the\n",
112 | "discrete linear system. We also cover the explicit elimination of essential boundary conditions and using the GLVis tool for visualization."
113 | ]
114 | },
115 | {
116 | "cell_type": "code",
117 | "execution_count": null,
118 | "metadata": {
119 | "tags": []
120 | },
121 | "outputs": [],
122 | "source": [
123 | "# Requires PyMFEM, see https://github.com/mfem/PyMFEM\n",
124 | "import mfem.ser as mfem"
125 | ]
126 | },
127 | {
128 | "cell_type": "code",
129 | "execution_count": null,
130 | "metadata": {},
131 | "outputs": [],
132 | "source": [
133 | "# Load the mesh from a local file\n",
134 | "# meshfile = '../../mfem/data/star.mesh'\n",
135 | "# mesh = mfem.Mesh(meshfile)\n",
136 | "\n",
137 | "# Alternatively, create a simple square mesh and refine it\n",
138 | "mesh = mfem.Mesh(5, 5, \"TRIANGLE\")\n",
139 | "mesh.UniformRefinement()\n",
140 | "\n",
141 | "# Create H1 finite element function space\n",
142 | "fec = mfem.H1_FECollection(2, mesh.Dimension()) # order=2\n",
143 | "fespace = mfem.FiniteElementSpace(mesh, fec) \n",
144 | "\n",
145 | "# Determine essential degrees of freedom (the whole boundary here)\n",
146 | "ess_tdof_list = mfem.intArray()\n",
147 | "ess_bdr = mfem.intArray([1]*mesh.bdr_attributes.Size())\n",
148 | "fespace.GetEssentialTrueDofs(ess_bdr, ess_tdof_list)\n",
149 | "\n",
150 | "# Define Bilinear and Linear forms for the Laplace problem -Δu=1\n",
151 | "one = mfem.ConstantCoefficient(1.0)\n",
152 | "a = mfem.BilinearForm(fespace)\n",
153 | "a.AddDomainIntegrator(mfem.DiffusionIntegrator(one))\n",
154 | "a.Assemble()\n",
155 | "b = mfem.LinearForm(fespace)\n",
156 | "b.AddDomainIntegrator(mfem.DomainLFIntegrator(one))\n",
157 | "b.Assemble()\n",
158 | "\n",
159 | "# Create a grid function for the solution and initialize with 0\n",
160 | "x = mfem.GridFunction(fespace);\n",
161 | "x.Assign(0.0)\n",
162 | "\n",
163 | "# Form the linear system, AX=B, for the FEM discretization\n",
164 | "A = mfem.OperatorPtr()\n",
165 | "B = mfem.Vector()\n",
166 | "X = mfem.Vector()\n",
167 | "a.FormLinearSystem(ess_tdof_list, x, b, A, X, B);\n",
168 | "print(\"Size of the linear system: \" + str(A.Height()))\n",
169 | "\n",
170 | "# Solve the system using PCG solver and get the solution in x\n",
171 | "Asm = mfem.OperatorHandle2SparseMatrix(A)\n",
172 | "Msm = mfem.GSSmoother(Asm)\n",
173 | "mfem.PCG(Asm, Msm, B, X, 1, 200, 1e-12, 0.0)\n",
174 | "a.RecoverFEMSolution(X, b, x)"
175 | ]
176 | },
177 | {
178 | "cell_type": "markdown",
179 | "metadata": {},
180 | "source": [
181 | "### Plot the Solution with GLVis"
182 | ]
183 | },
184 | {
185 | "cell_type": "code",
186 | "execution_count": null,
187 | "metadata": {
188 | "tags": []
189 | },
190 | "outputs": [],
191 | "source": [
192 | "# Plot the mesh + solution (all GLVis keys and mouse commands work)\n",
193 | "g = glvis((mesh, x))\n",
194 | "g"
195 | ]
196 | },
197 | {
198 | "cell_type": "code",
199 | "execution_count": null,
200 | "metadata": {},
201 | "outputs": [],
202 | "source": [
203 | "# Plot the mesh only\n",
204 | "glvis(mesh)"
205 | ]
206 | },
207 | {
208 | "cell_type": "code",
209 | "execution_count": null,
210 | "metadata": {},
211 | "outputs": [],
212 | "source": [
213 | "# Visualization with additional GLVis keys\n",
214 | "g = glvis((mesh,x), keys='ARjlmcbp**')\n",
215 | "g"
216 | ]
217 | }
218 | ],
219 | "metadata": {
220 | "kernelspec": {
221 | "display_name": "Python 3 (ipykernel)",
222 | "language": "python",
223 | "name": "python3"
224 | },
225 | "language_info": {
226 | "codemirror_mode": {
227 | "name": "ipython",
228 | "version": 3
229 | },
230 | "file_extension": ".py",
231 | "mimetype": "text/x-python",
232 | "name": "python",
233 | "nbconvert_exporter": "python",
234 | "pygments_lexer": "ipython3",
235 | "version": "3.10.12"
236 | }
237 | },
238 | "nbformat": 4,
239 | "nbformat_minor": 4
240 | }
241 |
--------------------------------------------------------------------------------
/examples/ex9.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "## MFEM Example 9\n",
8 | "\n",
9 | "Adapted from [PyMFEM/ex9.py]( https://github.com/mfem/PyMFEM/blob/master/examples/ex9.py).\n",
10 | "Compare with the [original Example 9](https://github.com/mfem/mfem/blob/master/examples/ex9.cpp) in MFEM.\n",
11 | "\n",
12 | "This example code solves the time-dependent advection equation\n",
13 | "\n",
14 | "\\begin{equation*}\n",
15 | "\\frac{\\partial u}{\\partial t} + v \\cdot \\nabla u = 0,\n",
16 | "\\label{advection}\\tag{1}\n",
17 | "\\end{equation*}\n",
18 | "\n",
19 | "where $v$ is a given fluid velocity, and \n",
20 | "\n",
21 | "\\begin{equation*}\n",
22 | "u_0(x)=u(0,x)\n",
23 | "\\label{advection2}\\tag{2}\n",
24 | "\\end{equation*}\n",
25 | "\n",
26 | "is a given initial condition.\n",
27 | "\n",
28 | "The example demonstrates the use of Discontinuous Galerkin (DG) bilinear forms in MFEM (face integrators), the use of explicit ODE time integrators, the definition of periodic boundary conditions through periodic meshes, as well as the use of GLVis for persistent visualization of a time-evolving solution."
29 | ]
30 | },
31 | {
32 | "cell_type": "code",
33 | "execution_count": null,
34 | "metadata": {
35 | "tags": []
36 | },
37 | "outputs": [],
38 |
39 | "source": [
40 | "# Pip installs pyglvis and pymfem if in a colab notebook. Can comment out if not required\n",
41 | "# PyMFEM requires numpy < 2.0.0 \n",
42 | "import importlib.metadata, os\n",
43 | "\n",
44 | "desired_numpy = \"1.26.4\"\n",
45 | "\n",
46 | "installed_numpy = importlib.metadata.version(\"numpy\")\n",
47 | "if installed_numpy != desired_numpy:\n",
48 | " print(f\"Installing NumPy {desired_numpy} (current: {installed_numpy})... \")\n",
49 | " print(\"Runtime will restart... Run again for changes to take effect.\")\n",
50 | " %pip install numpy=={desired_numpy} --prefer-binary --quiet\n",
51 | " os._exit(0)\n",
52 | "print(f\"Correct NumPy ({desired_numpy}) is installed\")\n",
53 | "\n",
54 | "%pip install --quiet glvis\n",
55 | "from __future__ import print_function\n",
56 | "from os.path import expanduser, join\n",
57 | "import time\n",
58 | "import numpy as np\n",
59 | "from numpy import sqrt, pi, cos, sin, hypot, arctan2\n",
60 | "from scipy.special import erfc\n",
61 | "import mfem.ser as mfem\n",
62 | "from mfem.ser import intArray\n",
63 | "from glvis import glvis, GlvisData\n"
64 | ]
65 | },
66 | {
67 | "cell_type": "code",
68 | "execution_count": null,
69 | "metadata": {
70 | "tags": []
71 | },
72 | "outputs": [],
73 | "source": [
74 | "# 1. Setup problem parameters\n",
75 | "\n",
76 | "problem = 0\n",
77 | "order = 3\n",
78 | "vis_steps = 5\n",
79 | "ode_solver_type = 4\n",
80 | "sleep_interval = .05\n",
81 | "\n",
82 | "if problem == 0:\n",
83 | " # Translating hump\n",
84 | " mesh = \"periodic-hexagon.mesh\"\n",
85 | " ref_levels = 2\n",
86 | " dt = 0.01\n",
87 | " t_final = 10\n",
88 | "elif problem == 1:\n",
89 | " # Rotating power sines\n",
90 | " mesh = \"periodic-square.mesh\"\n",
91 | " ref_levels = 2\n",
92 | " dt = 0.005\n",
93 | " t_final = 9\n",
94 | "elif problem == 2:\n",
95 | " # Rotating trigs\n",
96 | " mesh = \"disc-nurbs.mesh\"\n",
97 | " ref_levels = 3\n",
98 | " dt = 0.005\n",
99 | " t_final = 9\n",
100 | "elif problem == 3:\n",
101 | " # Twisting sines\n",
102 | " mesh = \"periodic-square.mesh\"\n",
103 | " ref_levels = 4\n",
104 | " dt = 0.0025\n",
105 | " t_final = 9\n",
106 | " vis_steps = 20"
107 | ]
108 | },
109 | {
110 | "cell_type": "code",
111 | "execution_count": null,
112 | "metadata": {
113 | "tags": []
114 | },
115 | "outputs": [],
116 | "source": [
117 | "# 2. Download the mesh from GitHub and read it. \n",
118 | "# We can handle geometrically periodic meshes in this code.\n",
119 | "\n",
120 | "mesh_url = f\"https://raw.githubusercontent.com/mfem/mfem/master/data/{mesh}\"\n",
121 | "mesh_str = !curl -s {mesh_url}\n",
122 | "with open(mesh, \"w\") as f:\n",
123 | " f.write(\"\\n\".join(mesh_str))\n",
124 | "mesh = mfem.Mesh(mesh, 1,1)\n",
125 | "dim = mesh.Dimension()"
126 | ]
127 | },
128 | {
129 | "cell_type": "code",
130 | "execution_count": null,
131 | "metadata": {
132 | "tags": []
133 | },
134 | "outputs": [],
135 | "source": [
136 | "# 3. Define the ODE solver used for time integration. \n",
137 | "# Several explicit Runge-Kutta methods are available.\n",
138 | "\n",
139 | "ode_solver = None\n",
140 | "if ode_solver_type == 1: ode_solver = mfem.ForwardEulerSolver()\n",
141 | "elif ode_solver_type == 2: ode_solver = mfem.RK2Solver(1.0)\n",
142 | "elif ode_solver_type == 3: ode_solver = mfem.RK3SSolver()\n",
143 | "elif ode_solver_type == 4: ode_solver = mfem.RK4Solver()\n",
144 | "elif ode_solver_type == 6: ode_solver = mfem.RK6Solver()\n",
145 | "else: print(f\"Unknown ODE solver type: {ode_solver_type}\")"
146 | ]
147 | },
148 | {
149 | "cell_type": "code",
150 | "execution_count": null,
151 | "metadata": {
152 | "tags": []
153 | },
154 | "outputs": [],
155 | "source": [
156 | "# 4. Refine the mesh to increase the resolution. In this example we do\n",
157 | "# 'ref_levels' of uniform refinement. If the mesh is of NURBS type,\n",
158 | "# we convert it to a (piecewise-polynomial) high-order mesh.\n",
159 | "\n",
160 | "for lev in range(ref_levels):\n",
161 | " mesh.UniformRefinement()\n",
162 | " if mesh.NURBSext:\n",
163 | " mesh.SetCurvature(max(order, 1))\n",
164 | " bb_min, bb_max = mesh.GetBoundingBox(max(order, 1))"
165 | ]
166 | },
167 | {
168 | "cell_type": "code",
169 | "execution_count": null,
170 | "metadata": {
171 | "tags": []
172 | },
173 | "outputs": [],
174 | "source": [
175 | "# 5. Define the discontinuous DG finite element space of the given\n",
176 | "# polynomial order on the refined mesh.\n",
177 | "\n",
178 | "fec = mfem.DG_FECollection(order, dim)\n",
179 | "fes = mfem.FiniteElementSpace(mesh, fec)\n",
180 | "\n",
181 | "print(\"Number of unknowns: \" + str(fes.GetVSize()))\n",
182 | "\n",
183 | "# Define coefficient using VecotrPyCoefficient and PyCoefficient\n",
184 | "# A user needs to define EvalValue method \n",
185 | "class velocity_coeff(mfem.VectorPyCoefficient):\n",
186 | " def EvalValue(self, x): \n",
187 | " dim = len(x)\n",
188 | " \n",
189 | " center = (bb_min + bb_max)/2.0\n",
190 | " # map to the reference [-1,1] domain \n",
191 | " X = 2 * (x - center) / (bb_max - bb_min)\n",
192 | " if problem == 0:\n",
193 | " if dim == 1: v = [1.0,]\n",
194 | " elif dim == 2: v = [sqrt(2./3.), sqrt(1./3)]\n",
195 | " elif dim == 3: v = [sqrt(3./6.), sqrt(2./6), sqrt(1./6.)]\n",
196 | " elif (problem == 1 or problem == 2):\n",
197 | " # Clockwise rotation in 2D around the origin \n",
198 | " w = pi/2\n",
199 | " if dim == 1: v = [1.0,]\n",
200 | " elif dim == 2: v = [w*X[1], - w*X[0]]\n",
201 | " elif dim == 3: v = [w*X[1], - w*X[0], 0]\n",
202 | " elif (problem == 3):\n",
203 | " # Clockwise twisting rotation in 2D around the origin\n",
204 | " w = pi/2\n",
205 | " d = max((X[0]+1.)*(1.-X[0]),0.) * max((X[1]+1.)*(1.-X[1]),0.)\n",
206 | " d = d ** 2\n",
207 | " if dim == 1: v = [1.0,]\n",
208 | " elif dim == 2: v = [d*w*X[1], - d*w*X[0]]\n",
209 | " elif dim == 3: v = [d*w*X[1], - d*w*X[0], 0]\n",
210 | " return v\n",
211 | " \n",
212 | " \n",
213 | "class u0_coeff(mfem.PyCoefficient):\n",
214 | " def EvalValue(self, x): \n",
215 | " dim = len(x)\n",
216 | " center = (bb_min + bb_max)/2.0\n",
217 | " # map to the reference [-1,1] domain \n",
218 | " X = 2 * (x - center) / (bb_max - bb_min)\n",
219 | " if (problem == 0 or problem == 1):\n",
220 | " if dim == 1:\n",
221 | " return exp(-40. * (X[0]-0.5)**2)\n",
222 | " elif (dim == 2 or dim == 3):\n",
223 | " rx = 0.45; ry = 0.25; cx = 0.; cy = -0.2; w = 10.\n",
224 | " if dim == 3:\n",
225 | " s = (1. + 0.25*cos(2 * pi * x[2]))\n",
226 | " rx = rx * s\n",
227 | " ry = ry * s\n",
228 | " return ( erfc( w * (X[0]-cx-rx)) * erfc(-w*(X[0]-cx+rx)) *\n",
229 | " erfc( w * (X[1]-cy-ry)) * erfc(-w*(X[1]-cy+ry)) )/16\n",
230 | " elif problem == 2:\n",
231 | " rho = hypot(x[0], x[1])\n",
232 | " phi = arctan2(x[1], x[0])\n",
233 | " return (sin(pi * rho) **2) * sin(3*phi)\n",
234 | " elif problem == 3:\n",
235 | " return sin(pi * X[0]) * sin(pi * X[1])\n",
236 | " \n",
237 | " return 0.0\n",
238 | "\n",
239 | "# Inflow boundary condition (zero for the problems considered in \n",
240 | "# this example)\n",
241 | "class inflow_coeff(mfem.PyCoefficient):\n",
242 | " def EvalValue(self, x):\n",
243 | " return 0"
244 | ]
245 | },
246 | {
247 | "cell_type": "code",
248 | "execution_count": null,
249 | "metadata": {
250 | "tags": []
251 | },
252 | "outputs": [],
253 | "source": [
254 | "# 6. Set up and assemble the bilinear and linear forms corresponding to \n",
255 | "# the DG discretization. The DGTraceIntegrator involves integrals over\n",
256 | "# mesh interior faces.\n",
257 | "\n",
258 | "velocity = velocity_coeff(dim)\n",
259 | "inflow = inflow_coeff()\n",
260 | "u0 = u0_coeff()\n",
261 | "\n",
262 | "m = mfem.BilinearForm(fes)\n",
263 | "m.AddDomainIntegrator(mfem.MassIntegrator())\n",
264 | "k = mfem.BilinearForm(fes)\n",
265 | "k.AddDomainIntegrator(mfem.ConvectionIntegrator(velocity, -1.0))\n",
266 | "k.AddInteriorFaceIntegrator(mfem.TransposeIntegrator(mfem.DGTraceIntegrator(velocity, 1.0, -0.5)))\n",
267 | "k.AddBdrFaceIntegrator(mfem.TransposeIntegrator(mfem.DGTraceIntegrator(velocity, 1.0, -0.5)))\n",
268 | "b = mfem.LinearForm(fes)\n",
269 | "b.AddBdrFaceIntegrator(mfem.BoundaryFlowIntegrator(inflow, velocity, -1.0, -0.5))\n",
270 | "\n",
271 | "m.Assemble()\n",
272 | "m.Finalize()\n",
273 | "skip_zeros = 0\n",
274 | "k.Assemble(skip_zeros)\n",
275 | "k.Finalize(skip_zeros)\n",
276 | "b.Assemble()\n",
277 | "\n",
278 | "# Initial conditions\n",
279 | "u = mfem.GridFunction(fes)\n",
280 | "u.ProjectCoefficient(u0)"
281 | ]
282 | },
283 | {
284 | "cell_type": "markdown",
285 | "metadata": {},
286 | "source": [
287 | "### Solve and Plot the Solution with GLVis"
288 | ]
289 | },
290 | {
291 | "cell_type": "code",
292 | "execution_count": null,
293 | "metadata": {
294 | "tags": []
295 | },
296 | "outputs": [],
297 | "source": [
298 | "g = glvis((mesh, u), 400, 400, keys=\"Agj*****\")\n",
299 | "g.render()\n",
300 | "\n",
301 | "class FE_Evolution(mfem.PyTimeDependentOperator):\n",
302 | " def __init__(self, M, K, b):\n",
303 | " mfem.PyTimeDependentOperator.__init__(self, M.Size())\n",
304 | " self.K = K\n",
305 | " self.M = M\n",
306 | " self.b = b\n",
307 | " self.z = mfem.Vector(M.Size())\n",
308 | " self.zp = np.zeros(M.Size())\n",
309 | " self.M_prec = mfem.DSmoother()\n",
310 | " self.M_solver = mfem.CGSolver()\n",
311 | " self.M_solver.SetPreconditioner(self.M_prec)\n",
312 | " self.M_solver.SetOperator(M)\n",
313 | " self.M_solver.iterative_mode = False\n",
314 | " self.M_solver.SetRelTol(1e-9)\n",
315 | " self.M_solver.SetAbsTol(0.0)\n",
316 | " self.M_solver.SetMaxIter(100)\n",
317 | " self.M_solver.SetPrintLevel(0)\n",
318 | "\n",
319 | " def Mult(self, x, y):\n",
320 | " self.K.Mult(x, self.z)\n",
321 | " self.z += b\n",
322 | " self.M_solver.Mult(self.z, y)\n",
323 | "\n",
324 | "adv = FE_Evolution(m.SpMat(), k.SpMat(), b)\n",
325 | "\n",
326 | "ode_solver.Init(adv)\n",
327 | "t = 0.0\n",
328 | "ti = 0\n",
329 | "time.sleep(1)\n",
330 | "while True:\n",
331 | " if t > t_final - dt/2: break\n",
332 | " t, dt = ode_solver.Step(u, t, dt)\n",
333 | " ti = ti + 1\n",
334 | " if ti % vis_steps == 0:\n",
335 | " g.update((mesh, u))\n",
336 | " time.sleep(sleep_interval)\n",
337 | " print(f\"time step: {ti}, time: {t:.2e}\", end=\"\\r\")"
338 | ]
339 | }
340 | ],
341 | "metadata": {
342 | "kernelspec": {
343 | "display_name": "Python 3 (ipykernel)",
344 | "language": "python",
345 | "name": "python3"
346 | },
347 | "language_info": {
348 | "codemirror_mode": {
349 | "name": "ipython",
350 | "version": 3
351 | },
352 | "file_extension": ".py",
353 | "mimetype": "text/x-python",
354 | "name": "python",
355 | "nbconvert_exporter": "python",
356 | "pygments_lexer": "ipython3",
357 | "version": "3.10.12"
358 | }
359 | },
360 | "nbformat": 4,
361 | "nbformat_minor": 4
362 | }
363 |
--------------------------------------------------------------------------------
/examples/plot.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "metadata": {},
7 | "outputs": [],
8 | "source": [
9 | "# Pip installs pyglvis and pymfem if in a colab notebook. Can comment out if not required\n",
10 | "# PyMFEM requires numpy < 2.0.0 \n",
11 | "import importlib.metadata, os\n",
12 | "\n",
13 | "desired_numpy = \"1.26.4\"\n",
14 | "\n",
15 | "installed_numpy = importlib.metadata.version(\"numpy\")\n",
16 | "if installed_numpy != desired_numpy:\n",
17 | " print(f\"Installing NumPy {desired_numpy} (current: {installed_numpy})... \")\n",
18 | " print(\"Runtime will restart... Run again for changes to take effect.\")\n",
19 | " %pip install numpy=={desired_numpy} --prefer-binary --quiet\n",
20 | " os._exit(0)\n",
21 | "print(f\"Correct NumPy ({desired_numpy}) is installed\")\n",
22 | "\n",
23 | "%pip install --quiet glvis\n",
24 | "from glvis import glvis, GlvisData\n"
25 | "import mfem.ser as mfem\n",
26 | "import ipywidgets as widgets\n",
27 | "from math import *"
28 | ]
29 | },
30 | {
31 | "cell_type": "code",
32 | "execution_count": null,
33 | "metadata": {},
34 | "outputs": [],
35 | "source": [
36 | "element_types = {\n",
37 | " \"2d\": [\"TRIANGLE\", \"QUADRILATERAL\"],\n",
38 | " \"3d\": [\"TETRAHEDRON\", \"HEXAHEDRON\"]\n",
39 | "}\n",
40 | "\n",
41 | "basis_map = {\n",
42 | " \"H1\": mfem.H1_FECollection,\n",
43 | " \"L2\": mfem.L2_FECollection\n",
44 | "}\n",
45 | "\n",
46 | "class callback_coeff(mfem.PyCoefficient):\n",
47 | " def EvalValue(self, x):\n",
48 | " return self.callback(x)\n",
49 | " \n",
50 | "class callback_vcoeff(mfem.VectorPyCoefficient):\n",
51 | " def EvalValue(self, x):\n",
52 | " return self.callback(x)\n",
53 | " \n",
54 | "# these have to be globals, if they are gc'd the runtime crashes (segfault)\n",
55 | "coeff = fec = fespace = None\n",
56 | "def generate(shape: tuple,\n",
57 | " element_type: str,\n",
58 | " order: int,\n",
59 | " mesh_order: int,\n",
60 | " surf: bool,\n",
61 | " basis: mfem.FiniteElementCollection, \n",
62 | " func: callable = None,\n",
63 | " transform: callable = None):\n",
64 | " global fec, fespace, coeff\n",
65 | " if element_type in element_types[\"2d\"]: shape = shape[:2]\n",
66 | " mesh = mfem.mesh.Mesh(*shape, element_type)\n",
67 | " if surf:\n",
68 | " mesh.SetCurvature(mesh_order, True, 3)\n",
69 | " else:\n",
70 | " mesh.SetCurvature(mesh_order)\n",
71 | " if transform is not None:\n",
72 | " mesh_coeff = callback_vcoeff(mesh.SpaceDimension())\n",
73 | " mesh_coeff.callback = transform\n",
74 | " mesh.Transform(mesh_coeff)\n",
75 | " fec = basis(order, mesh.Dimension())\n",
76 | " fespace = mfem.FiniteElementSpace(mesh, fec)\n",
77 | " if func is None: return mesh\n",
78 | " x = mfem.GridFunction(fespace)\n",
79 | " coeff = callback_coeff()\n",
80 | " coeff.callback = func\n",
81 | " x.ProjectCoefficient(coeff)\n",
82 | " return (mesh, x)"
83 | ]
84 | },
85 | {
86 | "cell_type": "code",
87 | "execution_count": null,
88 | "metadata": {},
89 | "outputs": [],
90 | "source": [
91 | "# Widget parts\n",
92 | "nx = widgets.IntSlider(description=\"nx:\", min=1, value=8, max=50, step=1, continuous_update=False)\n",
93 | "ny = widgets.IntSlider(description=\"ny:\", min=1, value=8, max=50, step=1, continuous_update=False)\n",
94 | "nz = widgets.IntSlider(description=\"nz:\", min=1, value=8, max=50, step=1, continuous_update=False)\n",
95 | "order = widgets.BoundedIntText(description='FEM Order:', value=2, min=1, step=1, continuous_update=False, layout={\"width\": \"130px\"})\n",
96 | "mesh_order = widgets.BoundedIntText(description='Mesh Order:', value=2, min=1, step=1, continuous_update=False, layout={\"width\": \"130px\"})\n",
97 | "surf = widgets.Checkbox(description='Surface Mesh', value=False)\n",
98 | "element_type = widgets.Dropdown(\n",
99 | " value=\"TRIANGLE\",\n",
100 | " options=[e for l in element_types.values() for e in l],\n",
101 | " description='Mesh Elements:',\n",
102 | " style={'description_width': 'initial'},\n",
103 | " layout={\"width\": \"240px\"}\n",
104 | ")\n",
105 | "basis = widgets.Dropdown(\n",
106 | " # value=basis_map.values()[0],\n",
107 | " options=basis_map.keys(),\n",
108 | " description=\"FEM Basis:\",\n",
109 | " style={'description_width': 'initial'},\n",
110 | " layout={\"width\": \"130px\"}\n",
111 | ")\n",
112 | "func = widgets.Textarea(\n",
113 | " disabled=False,\n",
114 | " layout={\"height\": \"56px\", \"width\": \"240px\"}\n",
115 | ")\n",
116 | "transform = widgets.Textarea(\n",
117 | " disabled=False,\n",
118 | " layout={\"height\": \"56px\", \"width\": \"240px\"}\n",
119 | ")\n",
120 | "g = glvis(mfem.mesh.Mesh(nx.value, ny.value, element_type.value))\n",
121 | "\n",
122 | "def toggle_dim(event=None):\n",
123 | " if element_type.value in element_types[\"2d\"]:\n",
124 | " nz.disabled = True\n",
125 | " func.placeholder = \"FEM Function, e.g. x+y\"\n",
126 | " if surf == True:\n",
127 | " transform.placeholder = \"Mesh Transformation, e.g. (x,y,xy)\"\n",
128 | " else:\n",
129 | " transform.placeholder = \"Mesh Transformation, e.g. (x+y,x-y)\"\n",
130 | " else:\n",
131 | " nz.disabled = False\n",
132 | " func.placeholder = \"FEM Function, e.g. x+y+z\"\n",
133 | " transform.placeholder = \"Mesh Transformation, e.g. (x+y,x-y,z)\"\n",
134 | " \n",
135 | "# Setup handlers\n",
136 | "def show(event=None):\n",
137 | " # GridFunction callback\n",
138 | " if func.value != \"\":\n",
139 | " def gfcb(x):\n",
140 | " local = {\n",
141 | " \"x\": x[0],\n",
142 | " \"y\": x[1],\n",
143 | " \"z\": x[2] if len(x) > 2 else 0\n",
144 | " }\n",
145 | " return eval(func.value, None, local)\n",
146 | " else:\n",
147 | " gfcb = None\n",
148 | " \n",
149 | " # Mesh callback\n",
150 | " if transform.value != \"\":\n",
151 | " def mcb(x):\n",
152 | " local = {\n",
153 | " \"x\": x[0],\n",
154 | " \"y\": x[1],\n",
155 | " \"z\": x[2] if (len(x) > 2 or surf == True) else 0\n",
156 | " }\n",
157 | " return eval(transform.value, None, local)\n",
158 | " else:\n",
159 | " mcb = None\n",
160 | " \n",
161 | " g.plot(generate(\n",
162 | " shape=(nx.value, ny.value, nz.value),\n",
163 | " element_type=element_type.value,\n",
164 | " order=order.value,\n",
165 | " mesh_order=mesh_order.value,\n",
166 | " surf=surf.value,\n",
167 | " basis=basis_map[basis.value],\n",
168 | " func=gfcb,\n",
169 | " transform=mcb\n",
170 | " ))\n",
171 | "\n",
172 | "button = widgets.Button(description=\"Update\")\n",
173 | "button.on_click(show)\n",
174 | "\n",
175 | "# Figure updates on any change to nx or ny\n",
176 | "nx.observe(show, names=\"value\")\n",
177 | "ny.observe(show, names=\"value\")\n",
178 | "nz.observe(show, names=\"value\")\n",
179 | "order.observe(show, names=\"value\")\n",
180 | "mesh_order.observe(show, names=\"value\")\n",
181 | "surf.observe(show, names=\"value\")\n",
182 | "element_type.observe(show, names=\"value\")\n",
183 | "basis.observe(show, names=\"value\")\n",
184 | "element_type.observe(toggle_dim, names=\"value\")\n",
185 | "\n",
186 | "centered = widgets.Layout(display='inline-flex',\n",
187 | " align_items='center',\n",
188 | " justify_content=\"center\")\n",
189 | "\n",
190 | "# Initial state\n",
191 | "toggle_dim()\n",
192 | "\n",
193 | "# Build widget\n",
194 | "widgets.HBox([\n",
195 | " g._widget,\n",
196 | " widgets.VBox([nx, ny, nz, \n",
197 | " element_type, \n",
198 | " mesh_order, \n",
199 | " surf, transform, \n",
200 | " basis, order, func, \n",
201 | " button], layout=centered)\n",
202 | "])"
203 | ]
204 | },
205 | {
206 | "cell_type": "code",
207 | "execution_count": null,
208 | "metadata": {},
209 | "outputs": [],
210 | "source": [
211 | "# Sample 2D Mesh Transformation\n",
212 | "\n",
213 | "def spiral(x, y):\n",
214 | " r = 1+x+4*y\n",
215 | " phi = 4*pi*y\n",
216 | " return (r*cos(phi), r*sin(phi))\n",
217 | "\n",
218 | "# Sample 2D Surface Mesh Transformation\n",
219 | "\n",
220 | "def breather(x, y):\n",
221 | " u = 13.2*(2*x-1)\n",
222 | " v = 37.4*(2*y-1)\n",
223 | " b = 0.4\n",
224 | " r = 1 - b*b\n",
225 | " w = sqrt(r)\n",
226 | " denom = b*((w*cosh(b*u))*(w*cosh(b*u)) + (b*sin(w*v))*(b*sin(w*v)))\n",
227 | " return(-u + (2*r*cosh(b*u)*sinh(b*u)) / denom,\n",
228 | " (2*w*cosh(b*u)*(-(w*cos(v)*cos(w*v)) - sin(v)*sin(w*v))) / denom,\n",
229 | " (2*w*cosh(b*u)*(-(w*sin(v)*cos(w*v)) + cos(v)*sin(w*v))) / denom)\n",
230 | "\n",
231 | "# Sample 3D Mesh Transformation\n",
232 | "\n",
233 | "def toroid(x, y, z):\n",
234 | " ns = 3 # Number of shifts\n",
235 | " R = 1.0 # Major radius of the torus\n",
236 | " r = 0.4 # Minor radius of the torus\n",
237 | " phi = 2.0 * pi * z;\n",
238 | " theta = phi * ns / 4;\n",
239 | " u = sqrt(2) * (y - 0.5) * r;\n",
240 | " v = sqrt(2) * (x - 0.5) * r;\n",
241 | " return((R + u * cos(theta) + v * sin(theta)) * cos(phi),\n",
242 | " (R + u * cos(theta) + v * sin(theta)) * sin(phi),\n",
243 | " v * cos(theta) - u * sin(theta))\n",
244 | "\n",
245 | "# Sample 2D Function\n",
246 | "\n",
247 | "def wave2d(x,y):\n",
248 | " r = sqrt((x-0.5)**2+(y-0.5)**2)\n",
249 | " return cos(8*pi*r)*(1-r)**2\n",
250 | " \n",
251 | "# Sample 3D Function\n",
252 | " \n",
253 | "def wave3d(x, y, z):\n",
254 | " r = sqrt((x-0.5)**2+(y-0.5)**2+(z-0.5)**2)\n",
255 | " return cos(4*pi*r)*(1-r)"
256 | ]
257 | }
258 | ],
259 | "metadata": {
260 | "kernelspec": {
261 | "display_name": "Python 3 (ipykernel)",
262 | "language": "python",
263 | "name": "python3"
264 | },
265 | "language_info": {
266 | "codemirror_mode": {
267 | "name": "ipython",
268 | "version": 3
269 | },
270 | "file_extension": ".py",
271 | "mimetype": "text/x-python",
272 | "name": "python",
273 | "nbconvert_exporter": "python",
274 | "pygments_lexer": "ipython3",
275 | "version": "3.10.12"
276 | }
277 | },
278 | "nbformat": 4,
279 | "nbformat_minor": 4
280 | }
281 |
--------------------------------------------------------------------------------
/glvis/__about__.py:
--------------------------------------------------------------------------------
1 | __version__ = "1.1.1"
2 |
--------------------------------------------------------------------------------
/glvis/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2010-2024, Lawrence Livermore National Security, LLC. Produced
2 | # at the Lawrence Livermore National Laboratory. All Rights reserved. See files
3 | # LICENSE and NOTICE for details. LLNL-CODE-443271.
4 | #
5 | # This file is part of the GLVis visualization tool and library. For more
6 | # information and source code availability see https://glvis.org.
7 | #
8 | # GLVis is free software; you can redistribute it and/or modify it under the
9 | # terms of the BSD-3 license. We welcome feedback and contributions, see file
10 | # CONTRIBUTING.md for details.
11 |
12 | from glvis.__about__ import __version__
13 | from glvis.widget import to_stream, glvis, GlvisWidget
14 | from glvis.util import GlvisData
15 |
16 | __all__ = ["to_stream", "glvis", "GlvisWidget", "GlvisData", "__version__"]
17 |
--------------------------------------------------------------------------------
/glvis/util.py:
--------------------------------------------------------------------------------
1 | import requests
2 |
3 | class _GlvisData(type):
4 | _keys = (
5 | "capacitor", "distance", "ex1", "ex2", "ex27", "ex3", "ex5", "ex9",
6 | "klein_bottle", "laghos", "mesh_explorer", "mfem_logo", "minimal_surface",
7 | "mobius_strip", "navier", "quad", "quadrature_1D", "quadrature_lor",
8 | "remhos", "shaper", "shifted", "snake"
9 | )
10 |
11 | @staticmethod
12 | def _get(key):
13 | url = f"https://github.com/GLVis/data/raw/master/streams/{key.replace('_','-')}.saved"
14 | response = requests.get(url)
15 | # success
16 | if response.status_code == 200:
17 | return response.content.decode('utf-8').rstrip("\n")
18 | else:
19 | print(f"Failed to download data from {url}")
20 |
21 | def __getattr__(self, key):
22 | if key in self._keys:
23 | return self._get(key)
24 | else:
25 | raise AttributeError(f"No data named {key}")
26 |
27 | def __dir__(self):
28 | return super().__dir__() + list(self._keys)
29 |
30 | class GlvisData(metaclass=_GlvisData):
31 | pass
32 |
--------------------------------------------------------------------------------
/glvis/widget.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2010-2024, Lawrence Livermore National Security, LLC. Produced
2 | // at the Lawrence Livermore National Laboratory. All Rights reserved. See files
3 | // LICENSE and NOTICE for details. LLNL-CODE-443271.
4 | //
5 | // This file is part of the GLVis visualization tool and library. For more
6 | // information and source code availability see https://glvis.org.
7 | //
8 | // GLVis is free software; you can redistribute it and/or modify it under the
9 | // terms of the BSD-3 license. We welcome feedback and contributions, see file
10 | // CONTRIBUTING.md for details.
11 |
12 | import glvis from "https://esm.sh/gh/glvis/releases-js@gh-pages/glvis-js-0.3";
13 |
14 | function render({ model, el }) {
15 | let div = document.createElement("div");
16 | div.setAttribute("id", glvis.rand_id());
17 | div.setAttribute("tabindex", "0");
18 | el.append(div);
19 |
20 | let width = () => model.get("width");
21 | let height = () => model.get("height");
22 | let glv = new glvis.State(div, width(), height());
23 |
24 | function set_size() {
25 | glv.setSize(width(), height());
26 | }
27 |
28 | function plot() {
29 | const data = model.get("data_str");
30 | const is_new_stream = model.get("is_new_stream");
31 | if (is_new_stream) {
32 | glv.display(data);
33 | } else {
34 | glv.update(data);
35 | }
36 | }
37 |
38 | function handle_message(msg, buffers) {
39 | if (msg.type === "screenshot") {
40 | if (msg.use_web) {
41 | glv.saveScreenshot(msg.name);
42 | } else {
43 | glv.getPNGAsB64().then((v) => {
44 | model.send({ type: "screenshot", name: msg.name, b64: v });
45 | });
46 | }
47 | }
48 | }
49 |
50 | // update
51 | model.on("change:width", set_size);
52 | model.on("change:height", set_size);
53 | model.on("change:data_str", plot);
54 | model.on("msg:custom", handle_message);
55 | plot();
56 | }
57 |
58 | export default { render };
59 |
--------------------------------------------------------------------------------
/glvis/widget.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2010-2024, Lawrence Livermore National Security, LLC. Produced
2 | # at the Lawrence Livermore National Laboratory. All Rights reserved. See files
3 | # LICENSE and NOTICE for details. LLNL-CODE-443271.
4 | #
5 | # This file is part of the GLVis visualization tool and library. For more
6 | # information and source code availability see https://glvis.org.
7 | #
8 | # GLVis is free software; you can redistribute it and/or modify it under the
9 | # terms of the BSD-3 license. We welcome feedback and contributions, see file
10 | # CONTRIBUTING.md for details.
11 |
12 | import anywidget
13 | import io
14 | from IPython.display import display as ipydisplay
15 | from traitlets import Unicode, Int, Bool
16 | from typing import Union, Tuple
17 | from pathlib import Path
18 | import base64
19 |
20 | try:
21 | from mfem._ser.mesh import Mesh
22 | from mfem._ser.gridfunc import GridFunction
23 | except:
24 | Mesh = object
25 | GridFunction = object
26 |
27 | Data = Union[Tuple[Mesh, GridFunction], Mesh, str]
28 |
29 | def to_stream(data: Data) -> str:
30 | if isinstance(data, str):
31 | return data
32 |
33 | sio = io.StringIO()
34 | if isinstance(data, tuple):
35 | sio.write("solution\n")
36 | data[0].WriteToStream(sio)
37 | data[1].WriteToStream(sio)
38 | elif isinstance(data, Mesh):
39 | sio.write("mesh\n")
40 | data.WriteToStream(sio)
41 | else:
42 | raise TypeError("Unknown data type")
43 | return sio.getvalue()
44 |
45 | class _GlvisWidgetCore(anywidget.AnyWidget):
46 | """
47 | This is the backend that inherits from AnyWidget. Because we don't want all of the AnyWidget
48 | properties/methods exposed to the user, the front-end class GlvisWidget is a composition
49 | of this object, rather than directly inheriting from AnyWidget.
50 |
51 | _esm must be specified, and is basically a giant string with all of the javascript code required
52 | It could be defined as a docstring here, but for organization and syntax highlighting, it is
53 | defined in `widget.js`, similar to the organization used by pyobsplot:
54 | https://github.com/juba/pyobsplot/blob/main/src/pyobsplot/widget.py
55 | """
56 | _esm = anywidget._file_contents.FileContents(
57 | Path(__file__).parent / "widget.js", start_thread=False
58 | )
59 |
60 | data_str = Unicode('').tag(sync=True)
61 | width = Int(640).tag(sync=True)
62 | height = Int(480).tag(sync=True)
63 | is_new_stream = Bool().tag(sync=True)
64 |
65 |
66 | class GlvisWidget:
67 | """
68 | Front-end class used to interactively visualize data.
69 | """
70 | def __init__(self, data: Data, width: int=640, height: int=480, keys=None):
71 | """
72 | Parameters
73 | ----------
74 | data : Union[Tuple[Mesh, GridFunction], Mesh, str]
75 | Data to be visualized. Can consist of the PyMFEM objects: (Mesh, GridFunction) or Mesh,
76 | or it can be read directly from a stream/string (see `examples/basic.ipynb`).
77 | width : int, optional
78 | Width of visualization
79 | height : int, optional
80 | Height of visualization
81 | keys : str, optional
82 | Keyboard commands to customize the visualization. Can also be typed into the widget
83 | after it is instantiated. For a full list of options, see the GLVis README:
84 | https://github.com/GLVis/glvis?tab=readme-ov-file#key-commands
85 | """
86 |
87 | self._widget = _GlvisWidgetCore()
88 | self.set_size(width, height)
89 | self._sync(data, is_new=True, keys=keys)
90 |
91 | # Automatically renders the widget - necessary because this is a wrapper class
92 | def _repr_mimebundle_(self, **kwargs):
93 | return self._widget._repr_mimebundle_(**kwargs)
94 |
95 | def set_size(self, width: int, height: int):
96 | self._widget.width = width
97 | self._widget.height = height
98 |
99 | def plot(self, data: Data, keys=None):
100 | self._sync(data, is_new=True, keys=keys)
101 |
102 | def update(self, data: Data, keys=None):
103 | self._sync(data, is_new=False, keys=keys)
104 |
105 | def _on_msg(self, _, content, buffers):
106 | if content.get("type", "") == "screenshot":
107 | data = content.get("b64", "")
108 | name = content.get("name", "glvis.png")
109 | if not data:
110 | print(f"unable to save {name}, bad data")
111 | return
112 | with open(name, "wb") as f:
113 | f.write(base64.decodebytes(data.encode('ascii')))
114 |
115 | def _sync(self, data: Data, is_new: bool=True, keys=None):
116 | self._widget.is_new_stream = is_new
117 | data_string = to_stream(data)
118 | if keys is not None:
119 | key_string = keys if isinstance(data, str) else f"keys {keys}"
120 | data_string += key_string
121 | self._widget.data_str = data_string
122 |
123 | def render(self):
124 | ipydisplay(self)
125 |
126 | # Constructor alias
127 | def glvis(data: Data, width: int=640, height: int=480, keys=None):
128 | return GlvisWidget(data, width, height, keys)
129 | glvis.__doc__ = GlvisWidget.__init__.__doc__
130 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["hatchling"]
3 | build-backend = "hatchling.build"
4 |
5 | [project]
6 | name = "glvis"
7 | dynamic = ["version"]
8 | description = "GLVis widget for Jupyter notebooks"
9 | readme = "README.md"
10 | license = { file = "LICENSE" }
11 | dependencies = [
12 | "mfem>=4.5.2",
13 | "anywidget>=0.9.9",
14 | "ipywidgets>=8.0.0" # required for _repr_mimebundle_
15 | ]
16 |
17 | [project.urls]
18 | homepage = "https://glvis.org"
19 | repostiory = "https://github.com/glvis/pyglvis"
20 |
21 | [tool.hatch.version]
22 | path = "glvis/__about__.py"
23 |
24 | [tool.hatch.build.targets.wheel]
25 | packages = ["glvis"]
26 |
--------------------------------------------------------------------------------