├── grudge ├── py.typed ├── version.py ├── __init__.py ├── interpolation.py ├── geometry │ └── __init__.py ├── projection.py ├── models │ └── __init__.py └── shortcuts.py ├── doc ├── upload-docs.sh ├── dof_desc.rst ├── geometry.rst ├── discretization.rst ├── models.rst ├── utils.rst ├── operators.rst ├── Makefile ├── references.rst ├── index.rst ├── conf.py └── misc.rst ├── .github ├── dependabot.yml └── workflows │ ├── autopush.yml │ └── ci.yml ├── .test-conda-env-py3.yml ├── .gitignore ├── contrib ├── maxima │ ├── ehcleanbase.mac │ ├── em_units.mac │ ├── bilinearmap.mac │ ├── ehclean.mac │ ├── ecleanbase.mac │ ├── cns.mac │ ├── pml.mac │ ├── maxwellrectcav3d.mac │ ├── maxwellbase.mac │ ├── bgk-flow.mac │ ├── maxwell.mac │ ├── wave.mac │ ├── gedney-pml.mac │ ├── abarbanel-pml.mac │ ├── em-modes.mac │ ├── myhelpers.mac │ ├── euler.mac │ └── eclean.mac └── notes │ ├── mystyle.ts │ ├── fluxfan.fig │ ├── em-cheat.tm │ ├── ip-primal.tm │ └── ip-primal-nu.tm ├── .editorconfig ├── requirements.txt ├── README.rst ├── examples ├── geometry.py ├── hello-grudge.py ├── maxwell │ └── cavities.py ├── wave │ ├── var-propagation-speed.py │ ├── wave-min-mpi.py │ └── wave-op-var-velocity.py ├── advection │ ├── weak.py │ └── var-velocity.py └── euler │ ├── vortex.py │ └── acoustic_pulse.py ├── test ├── test_trace_pair.py ├── test_metrics.py ├── mesh_data.py ├── test_euler_model.py ├── test_modal_connections.py ├── test_tools.py └── test_dt_utils.py ├── .gitlab-ci.yml └── pyproject.toml /grudge/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doc/upload-docs.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | rsync --verbose --archive --delete _build/html/ doc-upload:doc/grudge 4 | -------------------------------------------------------------------------------- /doc/dof_desc.rst: -------------------------------------------------------------------------------- 1 | Degree of freedom (DOF) descriptions 2 | ==================================== 3 | 4 | .. automodule:: grudge.dof_desc 5 | -------------------------------------------------------------------------------- /doc/geometry.rst: -------------------------------------------------------------------------------- 1 | Metric terms and transformations 2 | ================================ 3 | 4 | .. automodule:: grudge.geometry.metrics 5 | -------------------------------------------------------------------------------- /doc/discretization.rst: -------------------------------------------------------------------------------- 1 | Discretization Collection 2 | ========================= 3 | 4 | .. module:: grudge 5 | 6 | .. automodule:: grudge.discretization 7 | -------------------------------------------------------------------------------- /doc/models.rst: -------------------------------------------------------------------------------- 1 | Discontinuous Galerkin Models 2 | ============================= 3 | 4 | Compressible Euler Equations 5 | ---------------------------- 6 | 7 | .. automodule:: grudge.models.euler 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Set update schedule for GitHub Actions 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" 8 | 9 | # vim: sw=4 10 | -------------------------------------------------------------------------------- /doc/utils.rst: -------------------------------------------------------------------------------- 1 | Helper functions 2 | ================ 3 | 4 | Estimating stable time-steps 5 | ---------------------------- 6 | 7 | .. automodule:: grudge.dt_utils 8 | 9 | Array contexts 10 | -------------- 11 | 12 | .. automodule:: grudge.array_context 13 | 14 | Miscellaneous tools 15 | ------------------- 16 | 17 | .. automodule:: grudge.tools 18 | -------------------------------------------------------------------------------- /doc/operators.rst: -------------------------------------------------------------------------------- 1 | Discontinuous Galerkin operators 2 | ================================ 3 | 4 | .. automodule:: grudge.op 5 | .. automodule:: grudge.trace_pair 6 | 7 | 8 | Transferring data between discretizations 9 | ========================================= 10 | 11 | .. automodule:: grudge.projection 12 | 13 | Reductions 14 | ========== 15 | 16 | .. automodule:: grudge.reductions 17 | -------------------------------------------------------------------------------- /.test-conda-env-py3.yml: -------------------------------------------------------------------------------- 1 | name: test-conda-env-py3 2 | channels: 3 | - conda-forge 4 | - nodefaults 5 | dependencies: 6 | - git 7 | - numpy 8 | - libhwloc=2 9 | # pocl 3.1 needed for SVM functionality 10 | # pocl<7 because of https://github.com/inducer/grudge/issues/396 11 | - pocl>=3.1,<7 12 | - islpy 13 | - pyopencl 14 | - python=3 15 | - gmsh 16 | - jax 17 | 18 | # test scripts use ompi-specific arguments 19 | - openmpi 20 | - mpi4py 21 | -------------------------------------------------------------------------------- /grudge/version.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from importlib import metadata 4 | 5 | 6 | def _parse_version(version: str) -> tuple[tuple[int, ...], str]: 7 | import re 8 | 9 | m = re.match(r"^([0-9.]+)([a-z0-9]*?)$", VERSION_TEXT) 10 | assert m is not None 11 | 12 | return tuple(int(nr) for nr in m.group(1).split(".")), m.group(2) 13 | 14 | 15 | VERSION_TEXT = metadata.version("grudge") 16 | VERSION, VERSION_STATUS = _parse_version(VERSION_TEXT) 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | .sw[po] 3 | .*.sw[po] 4 | *~ 5 | *.pyc 6 | *.pyo 7 | *.egg-info 8 | MANIFEST 9 | dist 10 | setuptools*egg 11 | setuptools.pth 12 | distribute*egg 13 | distribute*tar.gz 14 | a.out 15 | hk 16 | test/*.pdf 17 | examples/*.pdf 18 | 19 | *.dot 20 | 21 | *.vtu 22 | *.vts 23 | 24 | run-debug-* 25 | 26 | *.vtu 27 | *.pvtu 28 | *.pvd 29 | *.dot 30 | *.vtk 31 | *.silo 32 | *.dat 33 | 34 | .cache 35 | .pytest_cache 36 | 37 | # pylint stuff 38 | .pylintrc.yml 39 | .run-pylint.py 40 | -------------------------------------------------------------------------------- /contrib/maxima/ehcleanbase.mac: -------------------------------------------------------------------------------- 1 | load("maxwellbase.mac"); 2 | 3 | assume(%chi>0); 4 | 5 | ehclean_Asimp:blockmat( 6 | max_Asimp, 7 | hstack( 8 | vstack(epsinv*muinv*covect(n),zeromatrix(3,1)), 9 | vstack(zeromatrix(3,1),epsinv*muinv*covect(n)) 10 | ), 11 | 12 | vstack( 13 | hstack(%chi^2*n,zeromatrix(1,3)), 14 | hstack(zeromatrix(1,3),%chi^2*n) 15 | ), 16 | zeromatrix(2,2) 17 | ); 18 | ehclean_A:max_invsubst(ehclean_Asimp); 19 | [ehclean_V, ehclean_D, ehclean_invV]:max_invsubst(hypdiagonalize(ehclean_Asimp)); 20 | -------------------------------------------------------------------------------- /contrib/maxima/em_units.mac: -------------------------------------------------------------------------------- 1 | kill(all); 2 | load("myhelpers.mac"); 3 | assume(m>0); 4 | assume(N>0); 5 | assume(s>0); 6 | 7 | N:kg*m/s^2; 8 | 9 | C : A*s; 10 | J : N*m; 11 | V : J/C; 12 | F : C/V; 13 | T : V*s/m^2; 14 | N : kg*m/s^2; 15 | 16 | u_c0 : m/s; 17 | u_mu0 : N / A^2; 18 | u_epsilon0 : 1/(u_c0^2*u_mu0); 19 | 20 | 21 | c0 : 299792458 * u_c0; 22 | mu0 : 4*%pi * 10^(-7) * u_mu0; 23 | epsilon0 : 1/(c0^2*mu0); 24 | 25 | u_E : V/m; 26 | u_D : C/m^2; 27 | u_B : T; 28 | u_H : A/m; 29 | u_J : A/m^2; 30 | u_t : s; 31 | u_x : m; 32 | 33 | u_sigma : u_J/u_E; 34 | 35 | u_ndE : u_E; 36 | u_ndH : sqrt(u_mu0/u_epsilon0) * u_H; 37 | 38 | -------------------------------------------------------------------------------- /.github/workflows/autopush.yml: -------------------------------------------------------------------------------- 1 | name: Gitlab mirror 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | autopush: 9 | name: Automatic push to gitlab.tiker.net 10 | if: startsWith(github.repository, 'inducer/') 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v6 14 | - run: | 15 | curl -L -O https://tiker.net/ci-support-v0 16 | . ./ci-support-v0 17 | mirror_github_to_gitlab 18 | 19 | env: 20 | GITLAB_AUTOPUSH_KEY: ${{ secrets.GITLAB_AUTOPUSH_KEY }} 21 | 22 | # vim: sw=4 23 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org/ 2 | # https://github.com/editorconfig/editorconfig-vim 3 | # https://github.com/editorconfig/editorconfig-emacs 4 | 5 | root = true 6 | 7 | [*] 8 | indent_style = space 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | 14 | [*.py] 15 | indent_size = 4 16 | 17 | [*.rst] 18 | indent_size = 4 19 | 20 | [*.cpp] 21 | indent_size = 2 22 | 23 | [*.hpp] 24 | indent_size = 2 25 | 26 | # There may be one in doc/ 27 | [Makefile] 28 | indent_style = tab 29 | 30 | # https://github.com/microsoft/vscode/issues/1679 31 | [*.md] 32 | trim_trailing_whitespace = false 33 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= -n 7 | SPHINXBUILD ?= python $(shell which sphinx-build) 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | mpi4py 3 | git+https://github.com/inducer/pytools.git#egg=pytools 4 | git+https://github.com/inducer/pymbolic.git#egg=pymbolic 5 | git+https://github.com/inducer/islpy.git#egg=islpy 6 | git+https://github.com/inducer/pyopencl.git#egg=pyopencl 7 | git+https://github.com/inducer/loopy.git#egg=loopy 8 | git+https://github.com/inducer/meshpy.git#egg=meshpy 9 | git+https://github.com/inducer/modepy.git#egg=modepy 10 | git+https://github.com/inducer/arraycontext.git#egg=arraycontext 11 | git+https://github.com/inducer/meshmode.git#egg=meshmode 12 | git+https://github.com/inducer/pyvisfile.git#egg=pyvisfile 13 | git+https://github.com/inducer/pymetis.git#egg=pymetis 14 | git+https://github.com/illinois-ceesd/logpyle.git#egg=logpyle 15 | git+https://github.com/inducer/pytato.git#egg=pytato 16 | 17 | # for test_wave_dt_estimate 18 | sympy 19 | -------------------------------------------------------------------------------- /doc/references.rst: -------------------------------------------------------------------------------- 1 | References 2 | ========== 3 | 4 | .. 5 | When adding references here, please use the demonstrated format: 6 | [FirstAuthor_pubyear] 7 | 8 | .. [Shewchuk_2002] J. R. Shewchuk (2002), 9 | *What Is a Good Linear Element? - Interpolation, Conditioning, Quality Measures*, 10 | In Proceedings of the 11th International Meshing Roundtable. 11 | `(link) `__ 12 | `(slides) `__ 13 | 14 | .. [Hesthaven_2008] J. S. Hesthaven and T. Warburton (2008), 15 | *Nodal Discontinuous Galerkin Methods: Algorithms, Analysis, and Applications*, 16 | Springer. 17 | `(doi) `__ 18 | 19 | .. [Chan_2016] J. Chan, R. J. Hewett, and T. Warburton (2016), 20 | *Weight-Adjusted Discontinuous Galerkin Methods: Curvilinear Meshes*, 21 | SIAM Journal on Scientific Computing. 22 | `(doi) `__ 23 | -------------------------------------------------------------------------------- /contrib/maxima/bilinearmap.mac: -------------------------------------------------------------------------------- 1 | kill(all); 2 | lflatten(l):=apply(append, l); 3 | dims:3; 4 | origpoints: [[-1],[1]]; 5 | for i:2 thru dims do 6 | origpoints:lflatten( 7 | makelist( 8 | makelist( 9 | append(opi, [j]) 10 | ,j, [-1, 1] 11 | ), 12 | opi, origpoints)); 13 | points : makelist(ascii(97+i), i, 0, length(origpoints)-1); 14 | 15 | vars:makelist(concat(v,ascii(97+i)), i, 0, dims-1); 16 | mapargs:append( 17 | [1], vars, 18 | lflatten( 19 | makelist( 20 | makelist(vars[i]*vars[j], j, i+1, length(vars)), 21 | i, 1, length(vars) 22 | ) 23 | ) 24 | ); 25 | 26 | /* Idea: (x,y)^T = MAT*(1,u,v,uv) */ 27 | mapmat:genmatrix(mm, dims, length(mapargs)); 28 | 29 | f:mapmat.mapargs; 30 | 31 | myjacobian(f, vars):=genmatrix( 32 | lambda([i,j], diff(f[i], vars[j])), 33 | length(f), length(vars)); 34 | 35 | fjac:myjacobian(f, vars); 36 | 37 | print("bilinear map jacobian:"); 38 | print(fjac); 39 | print("bilinear map jacobian det:"); 40 | print(expand(determinant(fjac))); 41 | -------------------------------------------------------------------------------- /contrib/maxima/ehclean.mac: -------------------------------------------------------------------------------- 1 | kill(all); 2 | load("ehcleanbase.mac"); 3 | 4 | /* ------------------------------------------------------------------------- */ 5 | /* flux */ 6 | /* ------------------------------------------------------------------------- */ 7 | 8 | ehclean_Dinc:ratsubst(c,1/(sqrt(%epsilon)*sqrt(%mu)), ehclean_D); 9 | print(ehclean_Dinc); 10 | ehclean_sflux:hyp_upwind_flux([-%chi*c,-c,c,%chi*c], ehclean_Dinc); 11 | 12 | print("eh-clean system flux in terms of characteristic variables:"); 13 | print(covect(ehclean_sflux)); 14 | 15 | /* FIXME: ehclean_V should not depend on epsilon and mu, but it does 16 | For now, make cp and cm equal. */ 17 | /* 18 | ehclean_sflux:subst( 19 | [cp=1/(sqrt(%epsilon)*sqrt(%mu)), 20 | cm=1/(sqrt(%epsilon)*sqrt(%mu)), 21 | %chip=%chi, 22 | %chim=%chi], 23 | ehclean_sflux); 24 | 25 | print("e-clean system flux in terms of physical variables:"); 26 | ehclean_wflux:fullhypsimp(ehclean_V.ev(ehclean_sflux, 27 | [sm=ehclean_sminw,sp=ehclean_spinw])); 28 | print(ehclean_wflux); 29 | */ 30 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to grudge's Documentation! 2 | ================================== 3 | 4 | Here's an example to solve the PDE 5 | 6 | .. math:: 7 | \begin{cases} 8 | u_t + 2\pi u_x = 0, \\ 9 | u(0, t) = -\sin(2\pi t), \\ 10 | u(x, 0) = \sin(x), 11 | \end{cases} 12 | 13 | on the domain :math:`x \in [0, 2\pi]`. We closely follow Chapter 3 of 14 | [Hesthaven_2008]_. 15 | 16 | 17 | .. literalinclude:: ../examples/hello-grudge.py 18 | :start-after: BEGINEXAMPLE 19 | :end-before: ENDEXAMPLE 20 | 21 | Plotting numerical solution ``uh`` in results in 22 | 23 | .. plot:: ../examples/hello-grudge.py 24 | 25 | Contents: 26 | 27 | .. toctree:: 28 | :maxdepth: 2 29 | 30 | discretization 31 | dof_desc 32 | geometry 33 | operators 34 | utils 35 | models 36 | references 37 | misc 38 | 🚀 Github 39 | 💾 Download Releases 40 | 41 | Indices and Tables 42 | ================== 43 | 44 | * :ref:`genindex` 45 | * :ref:`modindex` 46 | * :ref:`search` 47 | -------------------------------------------------------------------------------- /contrib/maxima/ecleanbase.mac: -------------------------------------------------------------------------------- 1 | load("maxwellbase.mac"); 2 | 3 | assume(%chi>0); 4 | 5 | /* 6 | eclean_Asimp:blockmat( 7 | max_Asimp, 8 | vstack(epsinv*muinv*covect(n),constmatrix(3,1,0)), 9 | hstack(%chi^2*n,constmatrix(1,3,0)), 10 | zeromatrix(1,1) 11 | ); 12 | */ 13 | 14 | eclean_Asimp:blockmat( 15 | max_Asimp, 16 | vstack(%chi*sqrt(epsinv*muinv)*covect(n),constmatrix(3,1,0)), 17 | hstack(%chi*sqrt(epsinv*muinv)*n,constmatrix(1,3,0)), 18 | zeromatrix(1,1) 19 | ); 20 | 21 | [eclean_V, eclean_D, eclean_invV]:max_invsubst(hypdiagonalize(eclean_Asimp)); 22 | eclean_A:max_invsubst(eclean_Asimp); 23 | 24 | eclean_wm:vstack(max_wm,[%phi[m]]); 25 | eclean_wp:vstack(max_wp,[%phi[p]]); 26 | 27 | eclean_sm:makelist(sm[i],i,1,length(eclean_D)); 28 | eclean_sp:makelist(sp[i],i,1,length(eclean_D)); 29 | 30 | eclean_sminw:hypsimp(eclean_invV.eclean_wm); 31 | eclean_spinw:hypsimp(eclean_invV.eclean_wp); 32 | 33 | eclean_wmins:hypsimp(eclean_V.eclean_sm); 34 | eclean_wpins:hypsimp(eclean_V.eclean_sp); 35 | 36 | eclean_Emins:makelist(eclean_wmins[i,1],i,1,3); 37 | eclean_Hmins:makelist(eclean_wmins[i,1],i,4,6); 38 | eclean_phimins:eclean_wmins[7,1]; 39 | -------------------------------------------------------------------------------- /contrib/notes/mystyle.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <\body> 6 | <\with|mode|math> 7 | ,>>>> 8 | 9 | >>>> 10 | 11 | >>>> 12 | 13 | >>>> 14 | 15 | >> 16 | 17 | >> 18 | 19 | | ->>>> 20 | 21 | > 22 | 23 | 24 | >)>> 25 | 26 | 27 | <\initial> 28 | <\collection> 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /contrib/maxima/cns.mac: -------------------------------------------------------------------------------- 1 | /* compare formulation in JSH/TW with 2 | * https://en.wikipedia.org/wiki/Navier%E2%80%93Stokes_equations#Compressible_flow_of_Newtonian_fluids 3 | */ 4 | 5 | kill(all); 6 | d:2; 7 | 8 | div(vec):=sum(diff(vec[i], coords[i]), i, 1, length(vec)); 9 | grad(f):=makelist(diff(f, coords[i]), i, 1, d); 10 | vlaplace(vec):=makelist(div(grad(vec[i])), i, 1, length(vec)); 11 | 12 | uvec:makelist([u,v,w][i], i, 1, d); 13 | coords:makelist([x,y,z][i], i, 1, d); 14 | 15 | depends(uvec, coords); 16 | 17 | dudx:genmatrix( 18 | lambda([i,j], diff(uvec[i], coords[j])), 19 | d, d); 20 | 21 | delta(i, j):=if i = j then 1 else 0; 22 | 23 | tau:mu*genmatrix( 24 | lambda([i,j], 25 | dudx[i,j] + dudx[j,i] - 2/3 * delta(i,j) * mat_trace(dudx)), 26 | d, d); 27 | 28 | rhou_rhs:makelist(div(tau[i]), i, 1, d); 29 | 30 | muv:0; 31 | rhou_rhs2:(1/3*mu+muv)*grad(div(uvec)) + mu*vlaplace(uvec); 32 | 33 | /* 34 | print(rhou_rhs); 35 | print(rhou_rhs2); 36 | */ 37 | print(ratsimp(rhou_rhs-rhou_rhs2)); 38 | 39 | e_rhs:div(uvec.tau); 40 | 41 | e_rhs2:(1/3*mu+muv)*grad(div(uvec)) + mu*makelist(div(uvec*grad(vec[i])), i, 1, d); 42 | 43 | print(e_rhs); 44 | print(e_rhs2); 45 | print(ratsimp(e_rhs-e_rhs2)); 46 | -------------------------------------------------------------------------------- /grudge/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | 4 | __copyright__ = "Copyright (C) 2015 Andreas Kloeckner" 5 | 6 | __license__ = """ 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | """ 25 | 26 | from grudge.discretization import ( 27 | DiscretizationCollection, 28 | make_discretization_collection, 29 | ) 30 | 31 | 32 | __all__ = [ 33 | "DiscretizationCollection", "make_discretization_collection" 34 | ] 35 | -------------------------------------------------------------------------------- /contrib/notes/fluxfan.fig: -------------------------------------------------------------------------------- 1 | #FIG 3.2 Produced by xfig version 3.2.5 2 | Landscape 3 | Center 4 | Metric 5 | A4 6 | 100.00 7 | Single 8 | -2 9 | 1200 2 10 | 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 11 | 1 1 1.00 60.00 120.00 12 | 2160 7290 8640 7290 13 | 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 14 | 1 1 1.00 60.00 120.00 15 | 5220 7290 5220 3960 16 | 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 17 | 5220 7290 7920 6120 18 | 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 19 | 5220 7290 6210 5220 20 | 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 21 | 5220 7290 4410 5130 22 | 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 23 | 5220 7290 3060 5580 24 | 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 25 | 5220 7290 3690 4950 26 | 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 27 | 5220 7290 7110 5220 28 | 4 1 0 50 -1 0 15 0.0000 6 195 360 8820 7380 $x$\001 29 | 4 1 0 50 -1 0 15 0.0000 6 195 315 5220 3780 $t$\001 30 | 4 1 0 50 -1 0 15 0.0000 6 210 1275 8010 6120 $\\lambda_n$\001 31 | 4 1 0 50 -1 0 15 0.0000 6 210 1275 4410 5040 $\\lambda_k$\001 32 | 4 1 0 50 -1 0 15 0.0000 6 195 585 6840 7020 $s^+$\001 33 | 4 1 0 50 -1 0 15 0.0000 6 195 525 3420 7020 $s^-$\001 34 | 4 1 0 50 -1 0 15 0.0000 6 210 1275 2880 5490 $\\lambda_1$\001 35 | 4 1 0 50 -1 0 15 0.0000 6 225 1080 5580 5670 $s^{*(k)}$\001 36 | 4 1 0 50 -1 0 15 0.0000 6 195 825 4320 5580 $\\cdots$\001 37 | 4 1 0 50 -1 0 15 0.0000 6 195 825 6390 5760 $\\cdots$\001 38 | 4 1 0 50 -1 0 15 0.0000 6 225 1770 6120 5040 $\\lambda_{k+1}$\001 39 | 4 1 0 50 -1 0 15 0.0000 6 225 1275 6750 6210 $s^{*(n-1)}$\001 40 | 4 1 0 50 -1 0 15 0.0000 6 195 570 4140 6210 $s^*$\001 41 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | grudge 2 | ====== 3 | 4 | .. image:: https://gitlab.tiker.net/inducer/grudge/badges/main/pipeline.svg 5 | :alt: Gitlab Build Status 6 | :target: https://gitlab.tiker.net/inducer/grudge/commits/main 7 | .. image:: https://github.com/inducer/grudge/actions/workflows/ci.yml/badge.svg 8 | :alt: Github Build Status 9 | :target: https://github.com/inducer/grudge/actions/workflows/ci.yml 10 | 11 | .. 12 | .. image:: https://badge.fury.io/py/grudge.png 13 | :alt: Python Package Index Release Page 14 | :target: https://pypi.org/project/grudge/ 15 | 16 | grudge helps you discretize discontinuous Galerkin operators, quickly 17 | and accurately. 18 | 19 | It relies on 20 | 21 | * `numpy `__ for arrays 22 | * `modepy `__ for modes and nodes on simplices 23 | * `meshmode `__ for modes and nodes on simplices 24 | * `loopy `__ for fast array operations 25 | * `pytest `__ for automated testing 26 | 27 | and, indirectly, 28 | 29 | * `PyOpenCL `__ as computational infrastructure 30 | 31 | PyOpenCL is likely the only package you'll have to install 32 | by hand, all the others will be installed automatically. 33 | 34 | .. image:: https://badge.fury.io/py/grudge.png 35 | :target: https://pypi..org/project/grudge 36 | 37 | Resources: 38 | 39 | * `documentation `__ 40 | * `wiki home page `__ 41 | * `source code via git `__ 42 | -------------------------------------------------------------------------------- /grudge/interpolation.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. currentmodule:: grudge.op 3 | 4 | Interpolation 5 | ------------- 6 | 7 | .. autofunction:: interp 8 | """ 9 | from __future__ import annotations 10 | 11 | 12 | __copyright__ = """ 13 | Copyright (C) 2021 University of Illinois Board of Trustees 14 | """ 15 | 16 | __license__ = """ 17 | Permission is hereby granted, free of charge, to any person obtaining a copy 18 | of this software and associated documentation files (the "Software"), to deal 19 | in the Software without restriction, including without limitation the rights 20 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 21 | copies of the Software, and to permit persons to whom the Software is 22 | furnished to do so, subject to the following conditions: 23 | 24 | The above copyright notice and this permission notice shall be included in 25 | all copies or substantial portions of the Software. 26 | 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 28 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 29 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 30 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 31 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 32 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 33 | THE SOFTWARE. 34 | """ 35 | 36 | 37 | from typing import TYPE_CHECKING 38 | 39 | 40 | if TYPE_CHECKING: 41 | from grudge.discretization import DiscretizationCollection 42 | 43 | 44 | # FIXME: Should revamp interp and make clear distinctions 45 | # between projection and interpolations. 46 | # Related issue: https://github.com/inducer/grudge/issues/38 47 | def interp(dcoll: DiscretizationCollection, src, tgt, vec): 48 | from warnings import warn 49 | warn("'interp' currently calls to 'project'", 50 | UserWarning, stacklevel=2) 51 | 52 | from grudge.projection import project 53 | 54 | return project(dcoll, src, tgt, vec) 55 | -------------------------------------------------------------------------------- /contrib/maxima/pml.mac: -------------------------------------------------------------------------------- 1 | kill(all); 2 | load("maxwellbase.mac"); 3 | 4 | /* 5 | See 6 | S.G. Johnson, “Notes on Perfectly Matched Layers,” 2008; 7 | https://math.mit.edu/~stevenj/18.369/pml.pdf. 8 | */ 9 | 10 | coords:[x,y,z]; 11 | allvars:append([t],coords); 12 | 13 | max_E:makelist(concat(E,i),i,coords); 14 | max_H:makelist(concat(H,i),i,coords); 15 | max_w:vstack(max_E,max_H); 16 | 17 | depends(max_w,allvars); 18 | 19 | /* 20 | The minus sign takes the operator from conservation form 21 | u_t + A u_x = 0 22 | into ODE form 23 | u_t = - A u_x. 24 | */ 25 | sigexpr(c):=(1+%i*sigma[c]/omega); 26 | 27 | pml_op:-subst(makelist(concat('n, i)=1/sigexpr(i)*concat(d, i), i, coords), max_A); 28 | orig_op:-subst(makelist(concat('n, i)=concat(d, i), i, coords), max_A); 29 | 30 | op_rhs(op_mat, state):=block([rhss], 31 | rhss:op_mat.state, 32 | 33 | for c in coords do 34 | for var in state do 35 | rhss:subst(diff(var, c), concat(d, c)*var, rhss), 36 | rhss); 37 | 38 | pml_rhs:op_rhs(pml_op, max_w); 39 | orig_rhs:op_rhs(orig_op, max_w); 40 | 41 | make_ode(my_rhs, state):= 42 | covect( 43 | makelist(-%i*%omega*state[i]=my_rhs[i,1], i, 1, length(state))); 44 | 45 | pml_eqns:make_ode(pml_rhs, max_w); 46 | orig_eqns:make_ode(orig_rhs, max_w); 47 | 48 | kill_denom(eqns):= 49 | covect(makelist( 50 | block([eqn], 51 | eqn:eqns[i,1], 52 | for c in coords do 53 | if not freeof(sigma[c], eqn) then 54 | eqn:eqn*sigexpr(c), 55 | lhs(eqn)=multthru(rhs(eqn))), 56 | i,1,length(eqns))); 57 | 58 | simpler_eqns:kill_denom(pml_eqns); 59 | 60 | /* 61 | auxvar_coeffs: 62 | auxvars:makelist(concat(X,i),i,1,length(auxvar_coeffs)); 63 | depends(auxvars,allvars); 64 | 65 | new_rhss:pml_rhs; 66 | for i: 1 thru length(auxvar_coeffs) do 67 | new_rhss:ratsubst(concat(X,i), %i/omega*auxvar_coeffs[i,1], new_rhss); 68 | 69 | new_nonaux_eqns:covect(makelist( 70 | diff(max_w[i], t) = new_rhss[i,1], 71 | i, 1, length(max_w))); 72 | aux_eqns:covect(makelist(diff(concat(X,i), t)=-auxvar_coeffs[i,1], i, 1, length(auxvar_coeffs))); 73 | */ 74 | -------------------------------------------------------------------------------- /contrib/maxima/maxwellrectcav3d.mac: -------------------------------------------------------------------------------- 1 | kill(all); 2 | load("eigen"); 3 | load("itensor"); 4 | load("diag"); 5 | 6 | /* -------------------------------------------------------------------------- */ 7 | /* Utilities */ 8 | /* -------------------------------------------------------------------------- */ 9 | mu: 1; 10 | epsilon: 1; 11 | 12 | coords:[x,y,z]; 13 | 14 | curl(x):=crossfunc(lambda([i,j], diff(x[j], coords[i]))); 15 | div(f):=sum(diff(f[j], coords[j]), j, 1, length(coords)); 16 | 17 | faraday(max_E, max_H):= curl(max_E) - %i * omega * max_H; 18 | ampere(max_E, max_H):= curl(max_H) + %i * omega * mu * epsilon * max_E; 19 | /* 20 | ampere(max_E, max_H):= curl(max_H) + %i * omega * max_E; 21 | */ 22 | 23 | 24 | crossfunc(f):=makelist( 25 | sum(sum( 26 | levi_civita([i,j,k])*f(j,k), 27 | j,1,3),k,1,3),i,1,3)$ 28 | 29 | crossprod(a,b):=crossfunc(lambda([j,k], a[j]*b[k])); 30 | 31 | /* -------------------------------------------------------------------------- */ 32 | /* Attempts */ 33 | /* -------------------------------------------------------------------------- */ 34 | nabla_t_squared(f):=diff(f, x, 2) + diff(f, y, 2); 35 | nabla_t(f):= [diff(f, x, 1) , diff(f, y, 1), 0]; 36 | 37 | /* 38 | gamma : sqrt((k_y^2 + k_x^2)/ (mu *epsilon)) ; 39 | */ 40 | omega : sqrt(k_x^2 + k_y^2 + k_z^2); 41 | gamma : sqrt((k_y^2 + k_x^2)) ; 42 | 43 | psi_cand:E_0*sin(k_x*x)*sin(k_y*y); 44 | 45 | 46 | 47 | wave_eqn(f, gamma_s):=nabla_t_squared(f) + mu*epsilon*gamma_s*f; 48 | gamma_s : gamma^2; 49 | 50 | /* The _t indicates transverse components (i.e. x and y components only) */ 51 | /* 52 | E_t(psi):=(-k_z/gamma_s)*sin(k_z * z)* nabla_t(psi); 53 | H_t(psi):=crossprod((%i * omega /gamma_s)*cos(k_z * z)* [0,0,1], nabla_t(psi)); 54 | */ 55 | E_t(psi):=(-k_z/gamma_s)*sin(k_z * z)* nabla_t(psi); 56 | H_t(psi):=crossprod((%i * omega * epsilon/gamma_s)*cos(k_z * z)* [0,0,1], nabla_t(psi)); 57 | 58 | 59 | /* These are used as the analytic solution for a rectangular cavity resonator 60 | with travsverse magnetic waves */ 61 | 62 | E : E_t(psi_cand) + [0,0,psi_cand * cos(k_z * z)]; 63 | H :H_t(psi_cand); 64 | 65 | Etime : E * %e ^ (- %i * omega * t); 66 | Htime : H * %e ^ (- %i * omega * t); 67 | 68 | 69 | trigrat(div(E)); 70 | trigrat(div(H)); 71 | trigrat(faraday(E,H)); 72 | trigrat(ampere(E,H)); 73 | 74 | -------------------------------------------------------------------------------- /grudge/geometry/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | 4 | __copyright__ = """ 5 | Copyright (C) 2021 University of Illinois Board of Trustees 6 | """ 7 | 8 | __license__ = """ 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in 17 | all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | THE SOFTWARE. 26 | """ 27 | 28 | 29 | from grudge.geometry.metrics import ( 30 | area_element, 31 | first_fundamental_form, 32 | forward_metric_derivative_mat, 33 | forward_metric_nth_derivative, 34 | inverse_first_fundamental_form, 35 | inverse_metric_derivative_mat, 36 | inverse_surface_metric_derivative, 37 | inverse_surface_metric_derivative_mat, 38 | mv_normal, 39 | normal, 40 | pseudoscalar, 41 | second_fundamental_form, 42 | shape_operator, 43 | summed_curvature, 44 | ) 45 | 46 | 47 | __all__ = ( 48 | "area_element", 49 | "first_fundamental_form", 50 | "forward_metric_derivative_mat", 51 | "forward_metric_nth_derivative", 52 | "inverse_first_fundamental_form", 53 | "inverse_metric_derivative_mat", 54 | "inverse_surface_metric_derivative", 55 | "inverse_surface_metric_derivative_mat", 56 | "mv_normal", 57 | "normal", 58 | "pseudoscalar", 59 | "second_fundamental_form", 60 | "shape_operator", 61 | "summed_curvature", 62 | ) 63 | -------------------------------------------------------------------------------- /contrib/notes/em-cheat.tm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <\body> 6 | > 7 | 8 | 1. Units: 9 | 10 | <\eqnarray*> 11 | ||>>|||=s>>>|]>||>>>|]>||A|mN>>>||||As>>>|||s|N*m>>>>> 12 | 13 | 14 | 2. Equations 15 | 16 | <\eqnarray*> 17 | >>||\H+J>>|>>||\E>>|\D>||>>|\B>||>>> 18 | 19 | 20 | <\equation*> 21 | B=\H,D=\E 22 | 23 | 24 | <\equation*> 25 | Z=|\>>,Y=|\>>. 26 | 27 | 28 | 3. Boundary conditions 29 | 30 | <\equation*> 31 | n\E=0,n\H=0. 32 | 33 | 34 | 4. Cross product, curl 35 | 36 | <\equation*> 37 | >>|>>|>>>>>\>>|>>|>>>>>=b-ab>>|b-ab>>|b-ab>>>>>,curl 38 | F=F-\F>>|F-\F>>|F-\F>>>>> 39 | 40 | 41 | <\equation*> 42 | a\(b\c)=b(a\c)-c(a\b) 43 | 44 | 45 | 46 | <\initial> 47 | <\collection> 48 | 49 | 50 | -------------------------------------------------------------------------------- /contrib/maxima/maxwellbase.mac: -------------------------------------------------------------------------------- 1 | load("myhelpers.mac"); 2 | 3 | assume(c>0); 4 | assume(mu>0); 5 | assume(epsilon>0); 6 | assume(epsinv>0); 7 | assume(muinv>0); 8 | 9 | /* A hyperbolic system matrix resulting from a curl */ 10 | curlmat(coord):=genmatrix( 11 | lambda ([i,j], levi_civita([coord,j,i])), 12 | 3,3); 13 | 14 | max_submat(i):=blockmat( 15 | zeromatrix(3,3), 16 | -epsinv*curlmat(i), /* epsinv = 1/epsilon */ 17 | muinv*curlmat(i), /* muinv = 1/mu */ 18 | zeromatrix(3,3) 19 | ); 20 | 21 | n:[nx,ny,nz]; 22 | 23 | max_Asimp:sum(n[i]*max_submat(i),i,1,3); 24 | max_A:subst([epsinv=1/epsilon,muinv=1/mu], max_Asimp); 25 | 26 | max_invsubst(x):=subst([epsinv=1/epsilon, muinv=1/mu], x); 27 | 28 | [max_V, max_D, max_invV]:max_invsubst(hypdiagonalize(max_Asimp)); 29 | 30 | max_Em:makelist(Em[i],i,1,3); 31 | max_Ep:makelist(Ep[i],i,1,3); 32 | max_Hm:makelist(Hm[i],i,1,3); 33 | max_Hp:makelist(Hp[i],i,1,3); 34 | max_wm:vstack(max_Em,max_Hm); 35 | max_wp:vstack(max_Ep,max_Hp); 36 | 37 | max_sm:makelist(sm[i],i,1,6); 38 | max_sp:makelist(sp[i],i,1,6); 39 | 40 | max_sminw:hypsimp(max_invV.max_wm); 41 | max_spinw:hypsimp(max_invV.max_wp); 42 | 43 | max_wmins:hypsimp(max_V.max_sm); 44 | max_wpins:hypsimp(max_V.max_sp); 45 | 46 | max_Emins:makelist(max_wmins[i,1],i,1,3); 47 | max_Hmins:makelist(max_wmins[i,1],i,4,6); 48 | 49 | /* ------------------------------------------------------------------------- */ 50 | /* Boundary conditions */ 51 | /* ------------------------------------------------------------------------- */ 52 | max_pecbdrywp:append(-max_Em,max_Hm); 53 | 54 | /* ------------------------------------------------------------------------- */ 55 | /* Rankine-Hugoniot flux */ 56 | /* ------------------------------------------------------------------------- */ 57 | 58 | max_Dinc:subst([1/(sqrt(epsilon)*sqrt(mu))=c], max_D); 59 | max_sflux:hyp_upwind_flux([-c,0,c], max_Dinc); 60 | 61 | /* FIXME: max_V should not depend on epsilon and mu, but it does 62 | For now, make cp and cm equal. */ 63 | 64 | max_sflux:subst( 65 | [cp=1/(sqrt(epsilon)*sqrt(mu)), cm=1/(sqrt(epsilon)*sqrt(mu))], 66 | max_sflux); 67 | 68 | max_wflux:fullhypsimp(max_V.ev(max_sflux, [sm=max_sminw,sp=max_spinw])); 69 | 70 | max_stronglocalpart:max_A.max_wm; 71 | 72 | max_strongwflux:max_stronglocalpart-max_wflux; 73 | 74 | /* ------------------------------------------------------------------------- */ 75 | /* vector components */ 76 | /* ------------------------------------------------------------------------- */ 77 | 78 | normalcomp(v):=(v.n)*n; 79 | tangentialcomp(v):=v-normalcomp(v); 80 | -------------------------------------------------------------------------------- /contrib/maxima/bgk-flow.mac: -------------------------------------------------------------------------------- 1 | kill(all); 2 | 3 | d:2; 4 | 5 | load("myhelpers.mac"); 6 | assume(sqrt_rt>0); 7 | assume(tau>0); 8 | assume(r>0); 9 | 10 | coords:[x,y,z]; 11 | n:makelist(concat(n, coords[k]), k, 1, d); 12 | 13 | sqrt_rt:r; 14 | /* 15 | w0:a1; 16 | w1:a2; 17 | w2:a3; 18 | w3:a4; 19 | w4:a5; 20 | w5:a6; 21 | */ 22 | 23 | /* ------------------------------------------------------------------------ */ 24 | /* do not edit -- automatically generated by BGKFlowOperator */ 25 | 26 | d:2; 27 | 28 | vars: [w0, w1, w2, w3, w4, w5]; 29 | 30 | depends(vars, coords); 31 | 32 | bgk_neg_rhss: [ 33 | sqrt_rt*(diff(w1, x) + diff(w2, y)), 34 | sqrt_rt*(diff(w0, x) + diff(w3, y) + sqrt(2)*diff(w4, x)), 35 | sqrt_rt*(diff(w0, y) + diff(w3, x) + sqrt(2)*diff(w5, y)), 36 | sqrt_rt*(diff(w1, y) + diff(w2, x)) + 1/tau*(w3 + (-1)*w1*w2/w0), 37 | sqrt(2)*sqrt_rt*diff(w1, x) + 1/tau*(w4 + (-1)*w1*w1/(sqrt(2)*w0)), 38 | sqrt(2)*sqrt_rt*diff(w2, y) + 1/tau*(w5 + (-1)*w2*w2/(sqrt(2)*w0)) 39 | ]; 40 | 41 | /* ------------------------------------------------------------------------ */ 42 | 43 | 44 | bgk_A:genmatrix( 45 | lambda([i,j], 46 | sum(n[k]*diff(bgk_neg_rhss[i], diff(vars[j], coords[k])), k, 1, d)), 47 | length(vars), length(vars)); 48 | 49 | /* diagonalize system ------------------------------------------------------- */ 50 | [bgk_V, bgk_D, bgk_invV]:hypdiagonalize(bgk_A); 51 | 52 | /* auxiliary variables ------------------------------------------------------ */ 53 | bgk_wm:makelist(concat(vars[i], m), i, 1, length(vars)); 54 | bgk_wp:makelist(concat(vars[i], p), i, 1, length(vars)); 55 | bgk_sminw:hypsimp(bgk_invV.bgk_wm); 56 | bgk_spinw:hypsimp(bgk_invV.bgk_wp); 57 | 58 | /* rankine-hugoniot flux ---------------------------------------------------- */ 59 | bgk_sflux:hyp_upwind_flux( 60 | [-sqrt(3)*sqrt_rt,-sqrt_rt,0,sqrt_rt,sqrt(3)*sqrt_rt], 61 | bgk_D); 62 | 63 | bgk_wflux:fullhypsimp(bgk_V.ev(bgk_sflux, [sm=bgk_sminw,sp=bgk_spinw])); 64 | 65 | bgk_stronglocalpart:subst(r=rm, bgk_A).bgk_wm; 66 | 67 | bgk_strongwflux:fullhypsimp(bgk_stronglocalpart-bgk_wflux); 68 | 69 | bgk_strongwflux:fullhypsimp(subst([rm=r,rp=r], bgk_strongwflux)); 70 | 71 | /* display flux expression -------------------------------------------------- */ 72 | display2d:false; 73 | 74 | dispsubst:append( 75 | makelist(concat(w, i, p)=w[i][ext], i, 0, length(vars)-1), 76 | makelist(concat(w, i, m)=w[i][int], i, 0, length(vars)-1), 77 | makelist(n[i]=normal[i-1], i, 1, d) 78 | ); 79 | 80 | print(makelist( 81 | subst(dispsubst, bgk_strongwflux[i,1]), 82 | i, 1, length(bgk_strongwflux))); 83 | 84 | -------------------------------------------------------------------------------- /examples/geometry.py: -------------------------------------------------------------------------------- 1 | """Minimal example of viewing geometric quantities.""" 2 | 3 | __copyright__ = """ 4 | Copyright (C) 2015 Andreas Kloeckner 5 | Copyright (C) 2021 University of Illinois Board of Trustees 6 | """ 7 | 8 | __license__ = """ 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in 17 | all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | THE SOFTWARE. 26 | """ 27 | 28 | import pyopencl as cl 29 | import pyopencl.tools as cl_tools 30 | 31 | from grudge import geometry, shortcuts 32 | from grudge.array_context import PyOpenCLArrayContext 33 | from grudge.discretization import make_discretization_collection 34 | 35 | 36 | def main(write_output: bool = True) -> None: 37 | cl_ctx = cl.create_some_context() 38 | queue = cl.CommandQueue(cl_ctx) 39 | 40 | allocator = cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)) 41 | actx = PyOpenCLArrayContext(queue, allocator=allocator) 42 | 43 | from meshmode.mesh import BTAG_ALL 44 | from meshmode.mesh.generation import generate_warped_rect_mesh 45 | 46 | mesh = generate_warped_rect_mesh(dim=2, order=4, nelements_side=6) 47 | dcoll = make_discretization_collection(actx, mesh, order=4) 48 | 49 | nodes = actx.thaw(dcoll.nodes()) 50 | bdry_nodes = actx.thaw(dcoll.nodes(dd=BTAG_ALL)) 51 | bdry_normals = geometry.normal(actx, dcoll, dd=BTAG_ALL) 52 | 53 | if write_output: 54 | vis = shortcuts.make_visualizer(dcoll) 55 | vis.write_vtk_file("geo.vtu", [("nodes", nodes)]) 56 | 57 | bvis = shortcuts.make_boundary_visualizer(dcoll) 58 | bvis.write_vtk_file("bgeo.vtu", [("bdry normals", bdry_normals), 59 | ("bdry nodes", bdry_nodes)]) 60 | 61 | 62 | if __name__ == "__main__": 63 | main() 64 | -------------------------------------------------------------------------------- /contrib/maxima/maxwell.mac: -------------------------------------------------------------------------------- 1 | kill(all); 2 | 3 | load("maxwellbase.mac"); 4 | 5 | /* ------------------------------------------------------------------------- */ 6 | 7 | max_radbdryspinw:makelist( 8 | if max_D[i,i] >= 0 then max_sminw[i,1] else 0, 9 | i, 1, 6)$ 10 | 11 | max_wradbdry:fullhypsimp(max_V.max_radbdryspinw); 12 | print("Radiation boundary condition for Maxwell's:"); 13 | print(max_wradbdry); 14 | 15 | max_knownwradbdry:max_wm+1/2*vstack( 16 | crossprod(n, crossprod(n, max_Em)) 17 | - sqrt(mu/epsilon)*crossprod(n, max_Hm), 18 | crossprod(n, crossprod(n, max_Hm)) 19 | + sqrt(epsilon/mu)*crossprod(n, max_Em) 20 | ); 21 | assert(norm_2_squared(hypsimp(max_knownwradbdry - max_wradbdry))=0); 22 | 23 | /* ------------------------------------------------------------------------- */ 24 | max_pecbdrywpins:ev(max_pecbdrywp,[Em=max_Emins, Hm=max_Hmins]); 25 | 26 | print("PEC bdry condition for Maxwell's in terms of characteristic variables (s)"); 27 | max_pecbdryspinw:fullhypsimp(max_invV.max_pecbdrywpins); 28 | print(max_pecbdryspinw); 29 | /* 30 | /* ------------------------------------------------------------------------- */ 31 | /* Simple ("stoopid") upwinded flux */ 32 | /* ------------------------------------------------------------------------- */ 33 | 34 | max_sfluxstoopid:max_D . makelist( 35 | if max_D[i,i] >= 0 then 36 | sm[i,1] 37 | else 38 | sp[i,1], 39 | i, 1, length(max_D)); 40 | 41 | max_wfluxstoopid:fullhypsimp(max_V . ev(max_sfluxstoopid, [sm=max_sminw,sp=max_spinw])); 42 | 43 | print("Stoopid upwind flux for Maxwell's in terms of characteristic variables:"); 44 | print(max_sfluxstoopid); 45 | print("Stoopid upwind flux for Maxwell's in terms of physical variables:"); 46 | print(max_wfluxstoopid); 47 | 48 | /* ------------------------------------------------------------------------- */ 49 | /* Rankine-Hugoniot flux */ 50 | /* ------------------------------------------------------------------------- */ 51 | 52 | print("Maxwell's flux in terms of characteristic variables:"); 53 | print(max_sflux); 54 | 55 | print("Maxwell's flux in terms of physical variables:"); 56 | print(max_wflux); 57 | 58 | /* ------------------------------------------------------------------------- */ 59 | /* known flux value*/ 60 | /* ------------------------------------------------------------------------- */ 61 | max_Z:sqrt(mu/epsilon)$ 62 | max_Y:sqrt(epsilon/mu)$ 63 | 64 | max_knownstrongwflux:ratsimp(vstack( 65 | -1/(2*epsilon) 66 | *(crossprod(n,(max_Hm-max_Hp)-1/max_Z*crossprod(n,max_Em-max_Ep))), 67 | 1/(2*mu) 68 | *(crossprod(n,(max_Em-max_Ep)+1/max_Y*crossprod(n,max_Hm-max_Hp))) 69 | ))$ 70 | 71 | assert(norm_2_squared(hypsimp( 72 | (max_strongwflux) 73 | -max_knownstrongwflux))=0); 74 | */ 75 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | import os 2 | from importlib import metadata 3 | from urllib.request import urlopen 4 | 5 | 6 | _conf_url = "https://tiker.net/sphinxconfig-v0.py" 7 | with urlopen(_conf_url) as _inf: 8 | exec(compile(_inf.read(), _conf_url, "exec"), globals()) 9 | 10 | extensions = globals()["extensions"] + [ 11 | "matplotlib.sphinxext.plot_directive"] 12 | 13 | copyright = "2015-2024, Grudge contributors" 14 | author = "Grudge contributors" 15 | release = metadata.version("grudge") 16 | version = ".".join(release.split(".")[:2]) 17 | 18 | intersphinx_mapping = { 19 | "arraycontext": ("https://documen.tician.de/arraycontext/", None), 20 | "loopy": ("https://documen.tician.de/loopy/", None), 21 | "jax": ("https://docs.jax.dev/en/latest/", None), 22 | "meshmode": ("https://documen.tician.de/meshmode/", None), 23 | "modepy": ("https://documen.tician.de/modepy/", None), 24 | "mpi4py": ("https://mpi4py.readthedocs.io/en/stable", None), 25 | "numpy": ("https://numpy.org/doc/stable/", None), 26 | "pytools": ("https://documen.tician.de/pytools/", None), 27 | "pymbolic": ("https://documen.tician.de/pymbolic/", None), 28 | "pyopencl": ("https://documen.tician.de/pyopencl/", None), 29 | "python": ("https://docs.python.org/3/", None), 30 | } 31 | 32 | 33 | # index-page demo uses pyopencl via plot_directive 34 | os.environ["PYOPENCL_TEST"] = "port:cpu" 35 | 36 | nitpick_ignore_regex = [ 37 | ["py:data|py:class", r"arraycontext.*ContainerTc"], 38 | ] 39 | 40 | 41 | sphinxconfig_missing_reference_aliases = { 42 | # numpy 43 | "np.floating": "class:numpy.floating", 44 | "DTypeLike": "obj:numpy.typing.DTypeLike", 45 | 46 | # mpi4py 47 | "Intracomm": "mpi4py.MPI.Intracomm", 48 | "MPI.Intracomm": "mpi4py.MPI.Intracomm", 49 | 50 | # pytools 51 | "ObjectArray2D": "obj:pytools.obj_array.ObjectArray2D", 52 | 53 | # pyopencl 54 | "cl_array.Allocator": "class:pyopencl.array.Allocator", 55 | 56 | # actx 57 | "arraycontext.typing.ArithArrayContainerT": 58 | "obj:arraycontext.ArithArrayContainerT", 59 | "ScalarLike": "obj:arraycontext.ScalarLike", 60 | "Array": "obj:arraycontext.Array", 61 | "ArrayContainer": "obj:arraycontext.ArrayContainer", 62 | "ArithArrayContainer": "obj:arraycontext.ArithArrayContainer", 63 | "ArithArrayContainerT": "obj:arraycontext.ArithArrayContainerT", 64 | "ArrayOrContainer": "obj:arraycontext.ArrayOrContainer", 65 | "ArrayOrArithContainer": "obj:arraycontext.ArrayOrArithContainer", 66 | "ArrayOrContainerOrScalarT": "obj:arraycontext.ArrayOrContainerOrScalarT", 67 | 68 | # meshmode 69 | "DOFArray": "meshmode.dof_array.DOFArray", 70 | "Mesh": "meshmode.mesh.Mesh", 71 | "Discretization": "class:meshmode.discretization.Discretization", 72 | 73 | # grudge 74 | "DiscretizationTag": "obj:grudge.dof_desc.DiscretizationTag", 75 | "VolumeTag": "obj:grudge.dof_desc.VolumeTag", 76 | "TracePair": "class:grudge.trace_pair.TracePair", 77 | } 78 | 79 | 80 | def setup(app): 81 | app.connect("missing-reference", process_autodoc_missing_reference) # noqa: F821 82 | -------------------------------------------------------------------------------- /test/test_trace_pair.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | 4 | __copyright__ = "Copyright (C) 2021 University of Illinois Board of Trustees" 5 | 6 | __license__ = """ 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | """ 25 | 26 | 27 | import logging 28 | 29 | import numpy as np 30 | 31 | import meshmode.mesh.generation as mgen 32 | from arraycontext import ArrayContextFactory, pytest_generate_tests_for_array_contexts 33 | from meshmode.dof_array import DOFArray 34 | 35 | from grudge.array_context import PytestPyOpenCLArrayContextFactory 36 | from grudge.discretization import make_discretization_collection 37 | from grudge.trace_pair import TracePair 38 | 39 | 40 | logger = logging.getLogger(__name__) 41 | pytest_generate_tests = pytest_generate_tests_for_array_contexts( 42 | [PytestPyOpenCLArrayContextFactory]) 43 | 44 | 45 | def test_trace_pair(actx_factory: ArrayContextFactory): 46 | """Simple smoke test for :class:`grudge.trace_pair.TracePair`.""" 47 | actx = actx_factory() 48 | dim = 3 49 | order = 1 50 | n = 4 51 | 52 | mesh = mgen.generate_regular_rect_mesh( 53 | a=(-1,)*dim, b=(1,)*dim, 54 | nelements_per_axis=(n,)*dim) 55 | 56 | dcoll = make_discretization_collection(actx, mesh, order=order) 57 | 58 | rng = np.random.default_rng(1234) 59 | 60 | def rand(): 61 | return DOFArray( 62 | actx, 63 | tuple(actx.from_numpy( 64 | rng.uniform(size=(grp.nelements, grp.nunit_dofs))) 65 | for grp in dcoll.discr_from_dd("vol").groups)) 66 | 67 | from grudge.dof_desc import DD_VOLUME_ALL 68 | interior = rand() 69 | exterior = rand() 70 | tpair = TracePair(DD_VOLUME_ALL, interior=interior, exterior=exterior) 71 | 72 | import grudge.op as op 73 | assert op.norm(dcoll, tpair.avg - 0.5*(exterior + interior), np.inf) == 0 74 | assert op.norm(dcoll, tpair.diff - (exterior - interior), np.inf) == 0 75 | assert op.norm(dcoll, tpair.int - interior, np.inf) == 0 76 | assert op.norm(dcoll, tpair.ext - exterior, np.inf) == 0 77 | -------------------------------------------------------------------------------- /grudge/projection.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. currentmodule:: grudge.op 3 | 4 | Projections 5 | ----------- 6 | 7 | .. autofunction:: project 8 | """ 9 | 10 | from __future__ import annotations 11 | 12 | 13 | __copyright__ = """ 14 | Copyright (C) 2021 University of Illinois Board of Trustees 15 | """ 16 | 17 | __license__ = """ 18 | Permission is hereby granted, free of charge, to any person obtaining a copy 19 | of this software and associated documentation files (the "Software"), to deal 20 | in the Software without restriction, including without limitation the rights 21 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 22 | copies of the Software, and to permit persons to whom the Software is 23 | furnished to do so, subject to the following conditions: 24 | 25 | The above copyright notice and this permission notice shall be included in 26 | all copies or substantial portions of the Software. 27 | 28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 29 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 30 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 31 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 32 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 33 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 34 | THE SOFTWARE. 35 | """ 36 | 37 | 38 | from typing import TYPE_CHECKING 39 | 40 | from grudge.dof_desc import ( 41 | BoundaryDomainTag, 42 | ToDOFDescConvertible, 43 | VolumeDomainTag, 44 | as_dofdesc, 45 | ) 46 | 47 | 48 | if TYPE_CHECKING: 49 | from arraycontext import ArrayOrContainerOrScalarT 50 | 51 | from grudge.discretization import DiscretizationCollection 52 | 53 | 54 | def project( 55 | dcoll: DiscretizationCollection, 56 | src: ToDOFDescConvertible, 57 | tgt: ToDOFDescConvertible, vec: ArrayOrContainerOrScalarT 58 | ) -> ArrayOrContainerOrScalarT: 59 | """Project from one discretization to another, e.g. from the 60 | volume to the boundary, or from the base to the an overintegrated 61 | quadrature discretization. 62 | 63 | :arg src: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. 64 | :arg tgt: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. 65 | :arg vec: a :class:`~meshmode.dof_array.DOFArray` or an 66 | :class:`~arraycontext.ArrayContainer` of them. 67 | :returns: a :class:`~meshmode.dof_array.DOFArray` or an 68 | :class:`~arraycontext.ArrayContainer` like *vec*. 69 | """ 70 | # {{{ process dofdesc arguments 71 | 72 | src_dofdesc = as_dofdesc(src) 73 | 74 | contextual_volume_tag = None 75 | if isinstance(src_dofdesc.domain_tag, VolumeDomainTag): 76 | contextual_volume_tag = src_dofdesc.domain_tag.tag 77 | elif isinstance(src_dofdesc.domain_tag, BoundaryDomainTag): 78 | contextual_volume_tag = src_dofdesc.domain_tag.volume_tag 79 | 80 | tgt_dofdesc = as_dofdesc(tgt, _contextual_volume_tag=contextual_volume_tag) 81 | 82 | del src 83 | del tgt 84 | 85 | # }}} 86 | 87 | if src_dofdesc == tgt_dofdesc: 88 | return vec 89 | 90 | return dcoll.connection_from_dds(src_dofdesc, tgt_dofdesc)(vec) 91 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | Python 3 POCL: 2 | script: 3 | - export PY_EXE=python3 4 | - export PYOPENCL_TEST=portable:cpu 5 | - export EXTRA_INSTALL="pybind11 numpy mako" 6 | - curl -L -O https://gitlab.tiker.net/inducer/ci-support/raw/main/build-and-test-py-project.sh 7 | - ". ./build-and-test-py-project.sh" 8 | tags: 9 | - python3 10 | - pocl 11 | - mpi 12 | except: 13 | - tags 14 | artifacts: 15 | reports: 16 | junit: test/pytest.xml 17 | 18 | Python 3 Intel: 19 | script: 20 | - export PY_EXE=python3 21 | - export EXTRA_INSTALL="pybind11 numpy mako" 22 | - source /opt/enable-intel-cl.sh 23 | - export PYOPENCL_TEST="intel(r):pu" 24 | - curl -L -O https://gitlab.tiker.net/inducer/ci-support/raw/main/build-and-test-py-project.sh 25 | - ". ./build-and-test-py-project.sh" 26 | tags: 27 | - python3 28 | - pocl 29 | - mpi 30 | except: 31 | - tags 32 | artifacts: 33 | reports: 34 | junit: test/pytest.xml 35 | 36 | Python 3 POCL Examples: 37 | script: 38 | - export PY_EXE=python3 39 | - export PYOPENCL_TEST=portable:cpu 40 | - export EXTRA_INSTALL="pybind11 numpy mako mpi4py pyvisfile pymetis" 41 | - curl -L -O https://gitlab.tiker.net/inducer/ci-support/raw/main/build-py-project-and-run-examples.sh 42 | - ". ./build-py-project-and-run-examples.sh" 43 | tags: 44 | - python3 45 | - pocl 46 | - large-node 47 | except: 48 | - tags 49 | 50 | Python 3 Intel Examples: 51 | script: 52 | - export PY_EXE=python3 53 | - source /opt/enable-intel-cl.sh 54 | - export PYOPENCL_TEST="intel(r):pu" 55 | - export EXTRA_INSTALL="pybind11 numpy mako mpi4py pyvisfile pymetis" 56 | - curl -L -O https://gitlab.tiker.net/inducer/ci-support/raw/main/build-py-project-and-run-examples.sh 57 | - ". ./build-py-project-and-run-examples.sh" 58 | tags: 59 | - python3 60 | - pocl 61 | - large-node 62 | except: 63 | - tags 64 | 65 | Python 3 Conda: 66 | tags: 67 | - linux 68 | - large-node 69 | script: | 70 | export PYOPENCL_TEST=portable:cpu 71 | CONDA_ENVIRONMENT=.test-conda-env-py3.yml 72 | curl -L -O https://gitlab.tiker.net/inducer/ci-support/raw/main/build-and-test-py-project-within-miniconda.sh 73 | 74 | # Shut up ibverbs about fork(), e.g. https://gitlab.tiker.net/inducer/grudge/-/jobs/220796 75 | export RDMAV_FORK_SAFE=1 76 | 77 | . ./build-and-test-py-project-within-miniconda.sh 78 | 79 | Python 3 Conda Examples: 80 | tags: 81 | - linux 82 | - large-node 83 | script: | 84 | CONDA_ENVIRONMENT=.test-conda-env-py3.yml 85 | curl -L -O https://gitlab.tiker.net/inducer/ci-support/raw/main/ci-support.sh 86 | . ci-support.sh 87 | build_py_project_in_conda_env 88 | 89 | # Shut up ibverbs about fork(), e.g. https://gitlab.tiker.net/inducer/grudge/-/jobs/220796 90 | export RDMAV_FORK_SAFE=1 91 | 92 | run_examples 93 | 94 | Documentation: 95 | script: | 96 | EXTRA_INSTALL="pybind11 numpy matplotlib" 97 | curl -L -O https://gitlab.tiker.net/inducer/ci-support/raw/main/build-docs.sh 98 | . ./build-docs.sh 99 | tags: 100 | - python3 101 | 102 | Ruff: 103 | script: 104 | - pipx install ruff 105 | - ruff check 106 | tags: 107 | - docker-runner 108 | except: 109 | - tags 110 | -------------------------------------------------------------------------------- /grudge/models/__init__.py: -------------------------------------------------------------------------------- 1 | """Base classes for operators.""" 2 | from __future__ import annotations 3 | 4 | 5 | __copyright__ = """ 6 | Copyright (C) 2007 Andreas Kloeckner 7 | Copyright (C) 2021 University of Illinois Board of Trustees 8 | """ 9 | 10 | __license__ = """ 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in 19 | all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | THE SOFTWARE. 28 | """ 29 | 30 | from abc import ABC, abstractmethod 31 | 32 | 33 | class Operator(ABC): # noqa: B024 34 | """A base class for Discontinuous Galerkin operators. 35 | 36 | You may derive your own operators from this class, but, at present 37 | this class provides no functionality. Its function is merely as 38 | documentation, to group related classes together in an inheritance 39 | tree. 40 | """ 41 | 42 | 43 | class HyperbolicOperator(Operator): 44 | """A base class for hyperbolic Discontinuous Galerkin operators.""" 45 | 46 | @abstractmethod 47 | def max_characteristic_velocity(self, actx, **kwargs): 48 | r"""Return a maximum characteristic wavespeed for the operator. 49 | 50 | :arg actx: a :class:`arraycontext.ArrayContext`. 51 | :arg \**kwargs: Optional keyword arguments for determining the 52 | max characteristic velocity of the operator. 53 | 54 | Return a :class:`~meshmode.dof_array.DOFArray` or scalar 55 | representing the (local or global) maximal characteristic velocity of 56 | the operator. 57 | """ 58 | 59 | def estimate_rk4_timestep(self, actx, dcoll, **kwargs): 60 | r"""Estimate the largest stable timestep for an RK4 method. 61 | 62 | :arg actx: a :class:`arraycontext.ArrayContext`. 63 | :arg dcoll: a :class:`grudge.discretization.DiscretizationCollection`. 64 | :arg \**kwargs: Optional keyword arguments for determining the 65 | max characteristic velocity of the operator. These are passed 66 | to :meth:`max_characteristic_velocity`. 67 | """ 68 | import grudge.op as op 69 | from grudge.dt_utils import characteristic_lengthscales 70 | 71 | wavespeeds = self.max_characteristic_velocity(actx, **kwargs) 72 | local_timesteps = ( 73 | characteristic_lengthscales(actx, dcoll) / wavespeeds 74 | ) 75 | 76 | return op.nodal_min(dcoll, "vol", local_timesteps) 77 | -------------------------------------------------------------------------------- /contrib/maxima/wave.mac: -------------------------------------------------------------------------------- 1 | kill(all); 2 | 3 | load("myhelpers.mac"); 4 | 5 | /* redefine this to change dimensionality: */ 6 | n:[nx,ny]; 7 | 8 | dims:length(n); 9 | 10 | assume(c>0); 11 | 12 | if dims = 1 then n:[1]; 13 | 14 | esymmatrix(n, v, i,j):=ematrix(n,n,v,i,j)+ematrix(n,n,v,j,i); 15 | wave_A:sum(n[i]*esymmatrix(dims+1, -c, 1+i,1),i,1,dims); 16 | 17 | [wave_V, wave_D, wave_invV]:hypdiagonalize(wave_A); 18 | 19 | wave_vm:makelist(concat(vm,i),i,1,dims); 20 | wave_vp:makelist(concat(vp,i),i,1,dims); 21 | wave_wm:append([um],wave_vm); 22 | wave_wp:append([up],wave_vp); 23 | 24 | wave_sm:makelist(concat(sm,i),i,1,length(wave_D)); 25 | wave_sp:makelist(concat(sp,i),i,1,length(wave_D)); 26 | 27 | wave_sminw:wave_invV.wave_wm; 28 | wave_spinw:wave_invV.wave_wp; 29 | 30 | wave_wmins:wave_V.wave_sm; 31 | wave_wpins:wave_V.wave_sp; 32 | 33 | wave_radbdryspinw:makelist( 34 | if wave_D[i,i] >= 0 then wave_sminw[i,1] else 0, 35 | i, 1, length(wave_D)); 36 | wave_radbdrywp:fullhypsimp(wave_V.wave_radbdryspinw); 37 | 38 | wave_dirbdryspinw:makelist( 39 | if wave_D[i,i] >= 0 then wave_sminw[i,1] else ubc, 40 | i, 1, length(wave_D)); 41 | wave_dirbdrywp:fullhypsimp(wave_V.wave_dirbdryspinw); 42 | 43 | print("Radiation boundary condition for the wave equation:"); 44 | print(wave_radbdrywp); 45 | 46 | print("Dirichlet boundary condition for the wave equation:"); 47 | print(expand(wave_dirbdrywp)); 48 | 49 | wave_known_dirbdrywp:vstack([-n.wave_vm/2 + um/2 + ubc], 50 | -n*um/2 + n*ubc - 1/2*n*(n.wave_vm) + wave_vm); 51 | 52 | assert(norm_2_squared(fullhypsimp(wave_known_dirbdrywp - wave_dirbdrywp))=0); 53 | 54 | print("Homogeneous-dirichlet in characteristic:"); 55 | print(fullhypsimp(wave_invV.vstack( 56 | -columnvector(first(wave_wmins)), 57 | rest(wave_wmins)) 58 | )); 59 | 60 | print("Homogeneous-Neumann in characteristic:"); 61 | print(fullhypsimp(wave_invV.vstack( 62 | columnvector(first(wave_wmins)), 63 | -rest(wave_wmins)) 64 | )); 65 | 66 | /* ------------------------------------------------------------------------- */ 67 | 68 | wave_eigenvalues:makelist(wave_D[i,i], i, 1, length(wave_D)); 69 | 70 | if member(0, wave_eigenvalues) then 71 | wave_sflux:hyp_upwind_flux([-c,0,c], wave_D) 72 | else 73 | wave_sflux:hyp_upwind_flux([-c,c], wave_D); 74 | wave_wflux:ratsimp(wave_V.ev(wave_sflux, [sm=wave_sminw,sp=wave_spinw])); 75 | wave_strongwflux:fullhypsimp(subst([c=cm], wave_A).wave_wm - wave_wflux); 76 | 77 | /* 78 | print("Wave equation flux in terms of characteristic variables:"); 79 | print(wave_sflux); 80 | 81 | 82 | print("Weak flux divided by (-c), as implemented in StrongWaveOperator:"); 83 | print(hypsimp(ev(wave_wflux, cp=c, cm=c)/(-c))); 84 | */ 85 | print("Wave equation weak flux in terms of physical variables:"); 86 | print(wave_wflux); 87 | 88 | print("Strong-form wave equation flux in terms of physical variables:"); 89 | /*print(wave_strongwflux);*/ 90 | 91 | print(fullhypsimp(ev(wave_strongwflux))); 92 | 93 | /* Closed-form expression for upwind flux */ 94 | wave_knownstrongwflux:vstack( 95 | [(cp*n.wave_vp - cm*n.wave_vm + cp*up-cm*um)/2], 96 | (cp*n*(n.wave_vp)-cm*n*(n.wave_vm) + cp*n*up - cm*n*um)/2 97 | ); 98 | 99 | assert(norm_2_squared(fullhypsimp(wave_knownstrongwflux - wave_strongwflux))=0); 100 | -------------------------------------------------------------------------------- /contrib/maxima/gedney-pml.mac: -------------------------------------------------------------------------------- 1 | kill(all); 2 | load("maxwellbase.mac"); 3 | 4 | /* 5 | See 6 | S.D. Gedney, “An anisotropic perfectly matched layer-absorbing medium for 7 | the truncation of FDTD lattices,” IEEE Transactions on Antennas and 8 | Propagation, vol. 44, 1996, S. 1630-1639. 9 | */ 10 | 11 | /* -------------------------------------------------------------------------- */ 12 | /* Variable declarations */ 13 | /* -------------------------------------------------------------------------- */ 14 | 15 | coords:[x,y,z]; 16 | allvars:append([t],coords); 17 | 18 | max_E:makelist(concat(E,i),i,coords); 19 | max_H:makelist(concat(H,i),i,coords); 20 | max_w:append(max_E,max_H); 21 | 22 | max_D:makelist(concat(D,i),i,coords); 23 | max_B:makelist(concat(B,i),i,coords); 24 | aux_w:append(max_D, max_B); 25 | 26 | depends(max_w,allvars); 27 | depends(aux_w,allvars); 28 | 29 | /* -------------------------------------------------------------------------- */ 30 | /* Utilities */ 31 | /* -------------------------------------------------------------------------- */ 32 | 33 | curl(x):=crossfunc(lambda([i,j], diff(x[j], coords[i]))); 34 | 35 | make_eqns(l, r):= makelist(l[i]=r[i], i, 1, length(l)); 36 | 37 | shift(l, amount):=makelist(l[mod(i-amount-1, length(l))+1], i, 1, length(l)); 38 | 39 | norm(x):=sum(x[i]^2, i, 1, length(x)); 40 | 41 | /* -------------------------------------------------------------------------- */ 42 | /* Operator building */ 43 | /* -------------------------------------------------------------------------- */ 44 | 45 | sigexpr(c):=(1+sigma[c]/(%i*omega*epsilon)); 46 | 47 | /* 48 | This here is *NOT* in conservation form 49 | u_t + A u_x = 0, 50 | but in ODE form 51 | u_t = (- A) u_x. 52 | ---------- rhs 53 | */ 54 | 55 | max_rhs:append(curl(max_H),-curl(max_E)); 56 | 57 | s:makelist(sigexpr(c), c, coords); 58 | sl:shift(s, -1); 59 | sr:shift(s, 1); 60 | sdiv:sr/s; 61 | 62 | max_D_values:epsilon*sdiv*max_E; 63 | max_B_values:mu*sdiv*max_H; 64 | 65 | main_eqns:expand(make_eqns( 66 | %i*omega * vstack( 67 | sl*max_D, 68 | sl*max_B 69 | ), 70 | max_rhs)); 71 | aux_eqns:factor(ratsimp( 72 | append( 73 | make_eqns(max_D, max_D_values), 74 | make_eqns(max_B, max_B_values) 75 | ))); 76 | aux_eqns:makelist( 77 | lhs(e)*denom(rhs(e))=num(rhs(e)), e, aux_eqns); 78 | 79 | make_time_derivatives(vars, expr):= 80 | block([e], 81 | e:expr, 82 | for v in vars do 83 | e:ratsubst(diff(v,t),%i*omega*v,e), 84 | e); 85 | 86 | aux_eqns:make_time_derivatives( 87 | max_w, 88 | make_time_derivatives(aux_w, expand(aux_eqns)/%i)); 89 | main_eqns:make_time_derivatives(aux_w, main_eqns); 90 | 91 | soln:solve( 92 | append(main_eqns,aux_eqns), 93 | makelist(diff(v,t),v,append(max_w,aux_w))); 94 | print(expand(covect(soln[1]))); 95 | 96 | /* -------------------------------------------------------------------------- */ 97 | /* Compare vs 'nice' shorter expression */ 98 | /* -------------------------------------------------------------------------- */ 99 | 100 | sig:makelist(sigma[c], c, coords); 101 | sigl:shift(sig, -1); 102 | sigr:shift(sig, 1); 103 | 104 | known:append( 105 | max_D/epsilon*(sig - sigl)/epsilon - max_E*sigr/epsilon 106 | + curl(max_H)/epsilon, 107 | 108 | max_B/mu*(sig - sigl)/epsilon - max_H*sigr/epsilon 109 | -curl(max_E)/mu, 110 | 111 | -sigl/epsilon*max_D+curl(max_H), 112 | -sigl/epsilon*max_B-curl(max_E) 113 | ); 114 | /* print(covect(expand(ratsimp(map(rhs, soln[1])-known))));*/ 115 | assert(norm(ratsimp(map(rhs, soln[1])-known))=0); 116 | -------------------------------------------------------------------------------- /contrib/maxima/abarbanel-pml.mac: -------------------------------------------------------------------------------- 1 | kill(all); 2 | load("eigen"); 3 | load("itensor"); 4 | load("diag"); 5 | 6 | /* 7 | See 8 | 9 | S. Abarbanel und D. Gottlieb, “On the construction and analysis of absorbing 10 | layers in CEM,” Applied Numerical Mathematics, vol. 27, 1998, S. 331-340. 11 | (eq 3.7-3.11) 12 | 13 | E. Turkel und A. Yefet, “Absorbing PML 14 | boundary layers for wave-like equations,” 15 | Applied Numerical Mathematics, vol. 27, 16 | 1998, S. 533-557. 17 | (eq. 4.10) 18 | 19 | */ 20 | 21 | /* -------------------------------------------------------------------------- */ 22 | /* Variable declarations */ 23 | /* -------------------------------------------------------------------------- */ 24 | 25 | coords:[x,y,z]; 26 | allvars:append([t],coords); 27 | 28 | max_E:makelist(concat(E,i),i,coords); 29 | max_H:makelist(concat(H,i),i,coords); 30 | max_w:append(max_E,max_H); 31 | 32 | max_P:makelist(concat(P,i),i,coords); 33 | max_Q:makelist(concat(Q,i),i,coords); 34 | aux_w:append(max_P, max_Q); 35 | 36 | depends(max_w,allvars); 37 | depends(aux_w,allvars); 38 | 39 | sig:makelist(concat(s,i),i,coords); 40 | 41 | depends(sig, coords); 42 | 43 | /* -------------------------------------------------------------------------- */ 44 | /* Utilities */ 45 | /* -------------------------------------------------------------------------- */ 46 | 47 | crossfunc(f):=makelist( 48 | sum(sum( 49 | levi_civita([i,j,k])*f(j,k), 50 | j,1,3),k,1,3),i,1,3)$ 51 | 52 | curl(x):=crossfunc(lambda([i,j], diff(x[j], coords[i]))); 53 | 54 | shift(l, amount):=makelist(l[mod(i-amount-1, length(l))+1], i, 1, length(l)); 55 | 56 | norm(x):=sum(x[i]^2, i, 1, length(x)); 57 | 58 | 59 | /* -------------------------------------------------------------------------- */ 60 | /* Operator building */ 61 | /* -------------------------------------------------------------------------- */ 62 | 63 | /* 64 | This here is *NOT* in conservation form 65 | u_t + A u_x = 0, 66 | but in ODE form 67 | u_t = (- A) u_x. 68 | ---------- rhs 69 | */ 70 | 71 | max_rhs:append(1/epsilon*curl(max_H),-1/mu*curl(max_E)); 72 | 73 | 74 | pml_rhs:makelist(0,i,1,6); 75 | aux_eqn:makelist(0,i,1,6); 76 | 77 | for mx:1 thru 3 do 78 | block([my,mz,sgn], 79 | my:mod(mx-1+1,3)+1, 80 | mz:mod(mx-1+2,3)+1, 81 | assert(levi_civita([mx,my,mz])=1), 82 | pml_rhs[mx]:pml_rhs[mx] 83 | +1/epsilon*diff(max_H[mz],coords[my]) 84 | -sig[my]/epsilon*(2*max_E[mx]+max_P[mx]) 85 | , 86 | pml_rhs[my]:pml_rhs[my] 87 | -1/epsilon*diff(max_H[mz],coords[mx]) 88 | -sig[mx]/epsilon*(2*max_E[my]+max_P[my]) 89 | , 90 | pml_rhs[3+mz]:pml_rhs[3+mz] 91 | +1/mu*diff(max_E[mx],coords[my]) 92 | -1/mu*diff(max_E[my],coords[mx]) 93 | +1/mu*diff(sig[mx]/epsilon,coords[mx])*max_Q[mx] 94 | +1/mu*diff(sig[my]/epsilon,coords[my])*max_Q[my] 95 | , 96 | aux_eqn[mx]:aux_eqn[mx] 97 | -diff(max_P[mx],t) 98 | +sig[my]/epsilon*max_E[mx] 99 | , 100 | aux_eqn[my]:aux_eqn[my] 101 | +sig[mx]/epsilon*max_E[my] 102 | , 103 | aux_eqn[3+mx]:aux_eqn[3+mx] 104 | -diff(max_Q[mx],t)-sig[mx]/epsilon*max_Q[mx] 105 | -max_E[my] -max_E[mz] 106 | ); 107 | 108 | pml_eqn:makelist(diff(max_w[i],t)=pml_rhs[i], i, 1, 6); 109 | aux_eqn:makelist(aux_eqn[i]=0, i, 1, 6); 110 | print(expand(covect(pml_eqn))); 111 | print(expand(covect(aux_eqn))); 112 | slist:[ 113 | /*Qx=-Q[y],Qy=Q[x],*/Qz=0, 114 | /*Px=P[y],Py=-P[x],*/Pz=0, 115 | sz=0, 116 | Hx=0,Hy=0,Ez=0, 117 | epsilon=1,mu=1]; 118 | 119 | print(expand(covect(subst(slist,pml_eqn)))); 120 | print(expand(covect(subst(slist,aux_eqn)))); 121 | 122 | load("em_units.mac"); 123 | u_P:u_sigma*u_E/u_epsilon0*u_t; 124 | u_Q:u_E*u_t; 125 | 126 | assert(u_E/u_t - u_sigma/u_epsilon0*u_P=0); 127 | assert(u_H/u_t - (1/u_mu0)*(u_sigma/u_epsilon0)/u_x*u_Q=0); 128 | 129 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | schedule: 8 | - cron: '17 3 * * 0' 9 | 10 | concurrency: 11 | group: ${{ github.head_ref || github.ref_name }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | ruff: 16 | name: Ruff 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v6 20 | - name: "Main Script" 21 | run: | 22 | pipx install ruff 23 | ruff check 24 | 25 | basedpyright: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v6 29 | - name: "Main Script" 30 | run: | 31 | curl -L -O https://tiker.net/ci-support-v0 32 | . ./ci-support-v0 33 | build_py_project_in_conda_env 34 | cipip install pytest modepy 35 | cipip install basedpyright 36 | basedpyright 37 | 38 | typos: 39 | name: Typos 40 | runs-on: ubuntu-latest 41 | steps: 42 | - uses: actions/checkout@v6 43 | - uses: crate-ci/typos@master 44 | 45 | pytest3: 46 | name: Pytest on Py3 47 | runs-on: ubuntu-latest 48 | steps: 49 | - uses: actions/checkout@v6 50 | - name: "Main Script" 51 | run: | 52 | curl -L -O https://gitlab.tiker.net/inducer/ci-support/raw/main/build-and-test-py-project-within-miniconda.sh 53 | . ./build-and-test-py-project-within-miniconda.sh 54 | 55 | pyexamples3: 56 | name: Examples on Py3 57 | runs-on: ubuntu-latest 58 | steps: 59 | - uses: actions/checkout@v6 60 | - name: "Main Script" 61 | run: | 62 | curl -L -O https://tiker.net/ci-support-v0 63 | . ci-support-v0 64 | build_py_project_in_conda_env 65 | run_examples 66 | 67 | python wave/wave-op-mpi.py --lazy 68 | python wave/wave-op-mpi.py --lazy --quad --nonaffine 69 | python euler/acoustic_pulse.py --lazy 70 | python euler/vortex.py --oi --lazy 71 | 72 | # --oversubscribe is an option for Open MPI (which is what the CI uses) 73 | # It allows the CI to succeed even if the CI runner does not 74 | # have a sufficient number of cores. 75 | mpiexec -np 2 --oversubscribe python wave/wave-op-mpi.py --lazy 76 | 77 | mpiexec -np 2 --oversubscribe python wave/wave-op-mpi.py --numpy 78 | 79 | docs: 80 | name: Documentation 81 | runs-on: ubuntu-latest 82 | steps: 83 | - uses: actions/checkout@v6 84 | - name: "Main Script" 85 | run: | 86 | echo "- matplotlib" >> .test-conda-env-py3.yml 87 | curl -L -O https://gitlab.tiker.net/inducer/ci-support/raw/main/ci-support.sh 88 | 89 | . ci-support.sh 90 | build_py_project_in_conda_env 91 | build_docs 92 | 93 | downstream_tests: 94 | strategy: 95 | matrix: 96 | downstream_project: [mirgecom] 97 | name: Tests for downstream project ${{ matrix.downstream_project }} 98 | runs-on: ubuntu-latest 99 | steps: 100 | - uses: actions/checkout@v6 101 | - name: "Main Script" 102 | env: 103 | DOWNSTREAM_PROJECT: ${{ matrix.downstream_project }} 104 | run: | 105 | curl -L -O -k https://tiker.net/ci-support-v0 106 | . ./ci-support-v0 107 | 108 | # https://github.com/inducer/grudge/issues/211 109 | export CISUPPORT_PARALLEL_PYTEST=no 110 | 111 | test_downstream "$DOWNSTREAM_PROJECT" 112 | 113 | 114 | # vim: sw=4 115 | -------------------------------------------------------------------------------- /examples/hello-grudge.py: -------------------------------------------------------------------------------- 1 | # Solves the PDE: 2 | # \begin{cases} 3 | # u_t + 2\pi u_x = 0, \\ 4 | # u(0, t) = -\sin(2\pi t), \\ 5 | # u(x, 0) = \sin(x), 6 | # \end{cases} 7 | # on the domain $x \in [0, 2\pi]$. We closely follow Chapter 3 of 8 | # "Nodal Discontinuous Galerkin Methods" by Hesthaven & Warburton. 9 | 10 | # BEGINEXAMPLE 11 | import numpy as np 12 | 13 | import pyopencl as cl 14 | from meshmode.array_context import PyOpenCLArrayContext 15 | from meshmode.mesh.generation import generate_box_mesh 16 | 17 | import grudge.geometry as geo 18 | import grudge.op as op 19 | from grudge.discretization import make_discretization_collection 20 | from grudge.dof_desc import FACE_RESTR_INTERIOR, BoundaryDomainTag, as_dofdesc 21 | 22 | 23 | ctx = cl.create_some_context() 24 | queue = cl.CommandQueue(ctx) 25 | actx = PyOpenCLArrayContext(queue) 26 | 27 | nel = 10 28 | coords = np.linspace(0, 2*np.pi, nel) 29 | mesh = generate_box_mesh((coords,), 30 | boundary_tag_to_face={"left": ["-x"], 31 | "right": ["+x"]}) 32 | dcoll = make_discretization_collection(actx, mesh, order=1) 33 | 34 | 35 | def initial_condition(x): 36 | # 'x' contains ndim arrays. 37 | # 'x[0]' gets the first coordinate value of all the nodes 38 | return actx.np.sin(x[0]) 39 | 40 | 41 | def left_boundary_condition(x, t): 42 | return actx.np.sin(x[0] - 2 * np.pi * t) 43 | 44 | 45 | def flux(dcoll, u_tpair): 46 | dd = u_tpair.dd 47 | velocity = np.array([2 * np.pi]) 48 | normal = geo.normal(actx, dcoll, dd) 49 | 50 | v_dot_n = np.dot(velocity, normal) 51 | u_upwind = actx.np.where(v_dot_n > 0, 52 | u_tpair.int, u_tpair.ext) 53 | return u_upwind * v_dot_n 54 | 55 | 56 | vol_discr = dcoll.discr_from_dd("vol") 57 | left_bndry = as_dofdesc(BoundaryDomainTag("left")) 58 | right_bndry = as_dofdesc(BoundaryDomainTag("right")) 59 | 60 | x_vol = actx.thaw(dcoll.nodes()) 61 | x_bndry = actx.thaw(dcoll.discr_from_dd(left_bndry).nodes()) 62 | 63 | uh = initial_condition(x_vol) 64 | 65 | dt = 0.001 66 | t = 0 67 | t_final = 0.5 68 | 69 | # timestepper loop 70 | while t < t_final: 71 | # extract the left boundary trace pair 72 | lbnd_tpair = op.bv_trace_pair(dcoll, 73 | dd=left_bndry, 74 | interior=uh, 75 | exterior=left_boundary_condition(x_bndry, t)) 76 | # extract the right boundary trace pair 77 | rbnd_tpair = op.bv_trace_pair(dcoll, 78 | dd=right_bndry, 79 | interior=uh, 80 | exterior=op.project(dcoll, "vol", 81 | right_bndry, uh)) 82 | # extract the trace pairs on the interior faces 83 | interior_tpair = op.local_interior_trace_pair(dcoll, uh) 84 | Su = op.weak_local_grad(dcoll, uh) 85 | 86 | lift = op.face_mass(dcoll, 87 | # left boundary weak-flux terms 88 | op.project(dcoll, 89 | left_bndry, "all_faces", 90 | flux(dcoll, lbnd_tpair)) 91 | # right boundary weak-flux terms 92 | + op.project(dcoll, 93 | right_bndry, "all_faces", 94 | flux(dcoll, rbnd_tpair)) 95 | # interior weak-flux terms 96 | + op.project(dcoll, 97 | FACE_RESTR_INTERIOR, "all_faces", 98 | flux(dcoll, interior_tpair))) 99 | 100 | duh_by_dt = op.inverse_mass(dcoll, 101 | np.dot([2 * np.pi], Su) - lift) 102 | 103 | # forward euler time step 104 | uh = uh + dt * duh_by_dt 105 | t += dt 106 | # ENDEXAMPLE 107 | 108 | 109 | # Plot the solution: 110 | def u_exact(x, t): 111 | return actx.np.sin(x[0] - 2 * np.pi * t) 112 | 113 | 114 | assert op.norm(dcoll, 115 | uh - u_exact(x_vol, t_final), 116 | p=2) <= 0.1 117 | import matplotlib.pyplot as plt 118 | 119 | 120 | plt.plot(actx.to_numpy(actx.np.ravel(x_vol[0][0])), 121 | actx.to_numpy(actx.np.ravel(uh[0])), label="Numerical") 122 | plt.plot(actx.to_numpy(actx.np.ravel(x_vol[0][0])), 123 | actx.to_numpy(actx.np.ravel(u_exact(x_vol, t_final)[0])), label="Exact") 124 | plt.xlabel("$x$") 125 | plt.ylabel("$u$") 126 | plt.legend() 127 | plt.show() 128 | -------------------------------------------------------------------------------- /contrib/maxima/em-modes.mac: -------------------------------------------------------------------------------- 1 | kill(all); 2 | load("eigen"); 3 | load("itensor"); 4 | load("diag"); 5 | 6 | /* -------------------------------------------------------------------------- */ 7 | /* Utilities */ 8 | /* -------------------------------------------------------------------------- */ 9 | 10 | curl(x):=crossfunc(lambda([i,j], diff(x[j], coords[i]))); 11 | div(f):=sum(diff(f[j], coords[j]), j, 1, length(coords)); 12 | 13 | assert(condition):=if not condition then error("Assertion violated") else true$ 14 | 15 | norm_2_squared(v):=v.v; 16 | 17 | crossfunc(f):=makelist( 18 | sum(sum( 19 | levi_civita([i,j,k])*f(j,k), 20 | j,1,3),k,1,3),i,1,3)$ 21 | 22 | crossprod(a,b):=crossfunc(lambda([j,k], a[j]*b[k])); 23 | 24 | /* -------------------------------------------------------------------------- */ 25 | /* Variable declarations */ 26 | /* -------------------------------------------------------------------------- */ 27 | 28 | coords:[x,y,z]; 29 | allvars:append([t],coords); 30 | 31 | mu: 1; 32 | epsilon: 1; 33 | c: 1/sqrt(mu*epsilon); 34 | 35 | epsilon_0: 1; 36 | mu_0: 1; 37 | c_0: 1/sqrt(mu_0*epsilon_0); 38 | 39 | max_B(max_H):= mu*max_H; 40 | max_D(max_E):= epsilon*max_E; 41 | 42 | /* SI conventions, assumed time dep: exp(%i*omega*t) */ 43 | faraday(max_E, max_H):= curl(max_E) + %i * omega * max_B(max_H); 44 | ampere(max_E, max_H):= curl(max_B(max_H)) - %i * omega / c_0**2 * max_E; 45 | 46 | div_e(max_E, max_H):= div(max_E); 47 | div_h(max_E, max_H):= div(max_H); 48 | 49 | maxwell_pde(max_E, max_H):=append( 50 | faraday(max_E, max_H), 51 | ampere(max_E, max_H), 52 | [div_e(max_E, max_H), div_h(max_E, max_H)]); 53 | 54 | /* 55 | spatial_deps:[ 56 | exp(%i*m*x)*exp(%i*n*y), 57 | exp(%i*m*x)*exp(-%i*n*y), 58 | exp(-%i*m*x)*exp(%i*n*y), 59 | exp(-%i*m*x)*exp(-%i*n*y) 60 | ]; 61 | */ 62 | 63 | spatial_deps:[ 64 | exp(+%i*m*x)*exp(+%i*n*y)*exp(+%i*l*z), 65 | exp(+%i*m*x)*exp(+%i*n*y)*exp(-%i*l*z), 66 | exp(+%i*m*x)*exp(-%i*n*y)*exp(+%i*l*z), 67 | exp(+%i*m*x)*exp(-%i*n*y)*exp(-%i*l*z) 68 | 69 | /* 70 | exp(-%i*m*x)*exp(+%i*n*y)*exp(+%i*l*z), 71 | exp(-%i*m*x)*exp(+%i*n*y)*exp(-%i*l*z), 72 | exp(-%i*m*x)*exp(-%i*n*y)*exp(+%i*l*z), 73 | exp(-%i*m*x)*exp(-%i*n*y)*exp(-%i*l*z) 74 | */ 75 | ]; 76 | 77 | declare(m, integer, n, integer, l, integer); 78 | 79 | get_maxwell_solution(spatial_dep):=block([ 80 | max_B, max_D, coeffs, max_E, max_H, faraday, ampere, div_e, div_h, maxwell_pde, soln 81 | ], 82 | max_B: mu*max_H, 83 | max_D: epsilon*max_E, 84 | 85 | coeffs: [ 86 | Exr, Eyr, Ezr, Hxr, Hyr, Hzr, 87 | Exi, Eyi, Ezi, Hxi, Hyi, Hzi 88 | ], 89 | 90 | max_E: [ 91 | (Exr+%i*Exi)*spatial_dep, 92 | (Eyr+%i*Eyi)*spatial_dep, 93 | (Ezr+%i*Ezi)*spatial_dep 94 | ], 95 | max_H: [ 96 | (Hxr+%i*Hxi)*spatial_dep, 97 | (Hyr+%i*Hyi)*spatial_dep, 98 | (Hzr+%i*Hzi)*spatial_dep 99 | ], 100 | 101 | soln:solve( 102 | maxwell_pde(max_E, max_H), 103 | append(coeffs, [omega])), 104 | 105 | family1: soln[1], 106 | omega_eq: family1[length(family1)], 107 | assert(lhs(omega_eq) = omega), 108 | 109 | [subst(family1, [max_E, max_H]), rhs(omega_eq)] 110 | ); 111 | 112 | maxwell_solutions:makelist( 113 | get_maxwell_solution(spatial_deps[i]), 114 | i, 1, length(spatial_deps)); 115 | 116 | omegas:makelist( 117 | maxwell_solutions[i][2], 118 | i, 1, length(maxwell_solutions)); 119 | display(omegas); 120 | 121 | max_E:ratsimp(sum( 122 | maxwell_solutions[i][1][1], 123 | i, 1, length(maxwell_solutions))); 124 | max_H:ratsimp(sum( 125 | maxwell_solutions[i][1][2], 126 | i, 1, length(maxwell_solutions))); 127 | 128 | print("Check Maxwell:"); 129 | print(ratsimp(subst([omega=omegas[1]],maxwell_pde(max_E,max_H)))); 130 | 131 | pec_bcs:append( 132 | realpart(crossprod([-1,0,0], subst(x=0, max_E))), 133 | realpart(crossprod([1,0,0], subst(x=%pi, max_E))), 134 | realpart(crossprod([0,-1,0], subst(y=0, max_E))), 135 | realpart(crossprod([0,1,0], subst(y=%pi, max_E))), 136 | [ 137 | realpart([-1,0,0].subst(x=0, max_H)), 138 | realpart([1,0,0].subst(x=%pi, max_H)), 139 | realpart([0,-1,0].subst(y=0, max_H)), 140 | realpart([0,1,0].subst(y=%pi, max_H)) 141 | ]); 142 | 143 | freevars: sublist( 144 | listofvars([max_E, max_H]), 145 | lambda([rvar], substring(string(rvar),1,2) = "%")); 146 | 147 | ev_pec_bcs:append( 148 | subst([x=0, y=0, z=0], pec_bcs), 149 | subst([x=0, y=0, z=%pi], pec_bcs), 150 | subst([x=0, y=%pi, z=%pi], pec_bcs) 151 | ); 152 | 153 | /* 154 | Fails: 155 | 156 | pec_soln:linsolve(pec_bcs, freevars); 157 | */ 158 | -------------------------------------------------------------------------------- /contrib/maxima/myhelpers.mac: -------------------------------------------------------------------------------- 1 | load("eigen"); 2 | load("itensor"); 3 | load("diag"); 4 | 5 | assert(condition):=if not condition then error("Assertion violated") else true$ 6 | 7 | norm_2_squared(v):=v.v; 8 | 9 | /* A constant matrix of size n x m */ 10 | constmatrix(n,m,c):=genmatrix(lambda ([i,j], c),n,m); 11 | 12 | vstack:append; 13 | 14 | hstack(a,b):=transpose(append(transpose(a),transpose(b))); 15 | 16 | blockmat(a11,a12,a21,a22):=vstack(hstack(a11,a12),hstack(a21,a22))$ 17 | 18 | crossfunc(f):=makelist( 19 | sum(sum( 20 | levi_civita([i,j,k])*f(j,k), 21 | j,1,3),k,1,3),i,1,3)$ 22 | 23 | crossprod(a,b):=crossfunc(lambda([j,k], a[j]*b[k])); 24 | 25 | subrange(v, start, stop):=makelist(v[i,1],i,start,stop); 26 | 27 | extract_diagonal(A):=makelist(A[i][i],i,1,length(A)); 28 | make_diagonal_matrix(v):=genmatrix( 29 | lambda([i,j], if i=j then v[i] else 0), 30 | length(v),length(v)); 31 | 32 | /* ------------------------------------------------------------------------- */ 33 | /* Simplification for expressions stemming from hyperbolic systems */ 34 | /* ------------------------------------------------------------------------- */ 35 | 36 | hypsimp(x):=ratsimp(ratsubst(1,n.n,x))$ 37 | 38 | fullhypsimp(x):=hypsimp( 39 | ratsubst( 40 | last(n)^2, 41 | 1-sum(n[i]^2,i,1,length(n)-1), 42 | x) 43 | ); 44 | 45 | /* ------------------------------------------------------------------------- */ 46 | /* diagonalize a given hyperbolic operator A */ 47 | /* ------------------------------------------------------------------------- */ 48 | 49 | hypdiagonalize(A):=block([evA, V, invV,D], 50 | evA:hypsimp(apply(append, eigenvectors(A)[2])), 51 | V:transpose(apply(matrix, evA)), 52 | invV:hypsimp(invert(V)), 53 | assert(hypsimp(V.invV)=ident(length(A))), 54 | D:hypsimp(invV.A.V), 55 | [V, D, invV]); 56 | 57 | /* ------------------------------------------------------------------------- */ 58 | /* compute upwind flux for a given operator with eigenvalues evs, sorted 59 | * in ascending order. 60 | * Sign assumptions for all variables occurring in evs must be in place. 61 | */ 62 | /* ------------------------------------------------------------------------- */ 63 | hyp_upwind_flux(evs, D):=block([evvars, Dp, Dm, n, midstates, states, unknowns], 64 | evvars:listofvars(evs), 65 | 66 | add_evvars_suffix(suffix, x):=subst(makelist(v=concat(''v, suffix), v, evvars), x), 67 | 68 | evsm:add_evvars_suffix(m, evs), 69 | evsp:add_evvars_suffix(p, evs), 70 | 71 | Dm:add_evvars_suffix(m, D), 72 | Dp:add_evvars_suffix(p, D), 73 | 74 | midstates:makelist(makelist(concat(s,state,i), i, 1, length(D)), 75 | state, 1, length(evs)-1), 76 | 77 | states:append( 78 | [makelist(concat(sm, i), i, 1, length(D))], 79 | midstates, 80 | [makelist(concat(sp,i), i, 1, length(D))]), 81 | 82 | unknowns:apply(append, midstates), 83 | 84 | result:if member(0, evs) then 85 | block([biasedD, veceqns, eqns, soln], 86 | biasedD:makelist( 87 | if evs[i] = 0 then [Dp,Dm] 88 | else if evs[i] > 0 then [Dp,Dp] 89 | else [Dm,Dm], 90 | i, 1, length(evs)), 91 | 92 | veceqns:apply(append, makelist( 93 | -(if evs[i] > 0 then evsp[i] else evsm[i]) *(states[i+1]-states[i]) 94 | +(biasedD[i][1].states[i+1]-biasedD[i][2].states[i]), 95 | i,1,length(evs))), 96 | 97 | eqns:makelist(veceqns[i,1], i, 1, length(veceqns)), 98 | 99 | soln:solve(eqns, unknowns), 100 | assert(length(soln)=1), 101 | 102 | for i: 1 thru length(evs) do 103 | if evs[i] = 0 then return(Dp.subst(soln[1], midstates[i])) 104 | ) 105 | else 106 | block([straddle_idx, Dstates, veceqns, eqns, soln], 107 | straddle_idx:for i: 1 thru length(evs)-1 do 108 | if (evs[i] < 0) and (evs[i+1] > 0) then return(i), 109 | 110 | flux:makelist(concat(flux,i),i,1,length(D)), 111 | 112 | unknowns:append(unknowns, flux), 113 | 114 | Dstates:append( 115 | [Dm.first(states)], 116 | makelist( 117 | if i = straddle_idx then flux 118 | else if evs[i] > 0 then Dp.midstates[i] 119 | else Dm.midstates[i], 120 | i, 1, length(midstates)), 121 | [Dp.last(states)]), 122 | 123 | veceqns:apply(append, makelist( 124 | -(if evs[i] > 0 then evsp[i] else evsm[i]) *(states[i+1]-states[i]) 125 | +(Dstates[i+1]-Dstates[i]), 126 | i,1,length(evs))), 127 | 128 | eqns:makelist(veceqns[i,1], i, 1, length(veceqns)), 129 | 130 | print(covect(eqns)), 131 | soln:solve(eqns, unknowns), 132 | assert(length(soln)=1), 133 | 134 | subst(soln[1], flux) 135 | ), 136 | subst( 137 | append( 138 | makelist(concat(sm, i)=sm[i,1], i, 1, length(D)), 139 | makelist(concat(sp, i)=sp[i,1], i, 1, length(D)) 140 | ), 141 | result) 142 | ); 143 | -------------------------------------------------------------------------------- /test/test_metrics.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | 4 | __copyright__ = """ 5 | Copyright (C) 2015 Andreas Kloeckner 6 | Copyright (C) 2021 University of Illinois Board of Trustees 7 | """ 8 | 9 | __license__ = """ 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in 18 | all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | THE SOFTWARE. 27 | """ 28 | 29 | import logging 30 | 31 | import numpy as np 32 | import pytest 33 | 34 | import meshmode.mesh.generation as mgen 35 | from arraycontext import ArrayContextFactory, pytest_generate_tests_for_array_contexts 36 | from meshmode.dof_array import flat_norm 37 | 38 | from grudge.array_context import ( 39 | PytestNumpyArrayContextFactory, 40 | PytestPyOpenCLArrayContextFactory, 41 | PytestPytatoJAXArrayContextFactory, 42 | PytestPytatoPyOpenCLArrayContextFactory, 43 | ) 44 | from grudge.discretization import make_discretization_collection 45 | 46 | 47 | logger = logging.getLogger(__name__) 48 | pytest_generate_tests = pytest_generate_tests_for_array_contexts( 49 | [PytestPyOpenCLArrayContextFactory, 50 | PytestPytatoPyOpenCLArrayContextFactory, 51 | PytestNumpyArrayContextFactory, 52 | PytestPytatoJAXArrayContextFactory]) 53 | 54 | 55 | # {{{ inverse metric 56 | 57 | @pytest.mark.parametrize("dim", [2, 3]) 58 | @pytest.mark.parametrize("nonaffine", [False, True]) 59 | @pytest.mark.parametrize("use_quad", [False, True]) 60 | def test_inverse_metric(actx_factory: ArrayContextFactory, dim, nonaffine, use_quad): 61 | actx = actx_factory() 62 | 63 | order = 3 64 | mesh = mgen.generate_regular_rect_mesh(a=(-0.5,)*dim, b=(0.5,)*dim, 65 | nelements_per_axis=(6,)*dim, order=order) 66 | 67 | if nonaffine: 68 | def m(x): 69 | result = np.empty_like(x) 70 | result[0] = ( 71 | 1.5*x[0] + np.cos(x[0]) 72 | + 0.1*np.sin(10*x[1])) 73 | result[1] = ( 74 | 0.05*np.cos(10*x[0]) 75 | + 1.3*x[1] + np.sin(x[1])) 76 | if len(x) == 3: 77 | result[2] = x[2] 78 | return result 79 | 80 | from meshmode.mesh.processing import map_mesh 81 | mesh = map_mesh(mesh, m) 82 | 83 | from meshmode.discretization.poly_element import ( 84 | QuadratureSimplexGroupFactory, 85 | default_simplex_group_factory, 86 | ) 87 | 88 | from grudge.dof_desc import DISCR_TAG_BASE, DISCR_TAG_QUAD, as_dofdesc 89 | 90 | dcoll = make_discretization_collection( 91 | actx, mesh, 92 | discr_tag_to_group_factory={ 93 | DISCR_TAG_BASE: default_simplex_group_factory(base_dim=dim, order=order), 94 | DISCR_TAG_QUAD: QuadratureSimplexGroupFactory(2*order + 1), 95 | } 96 | ) 97 | 98 | from grudge.geometry import ( 99 | forward_metric_derivative_mat, 100 | inverse_metric_derivative_mat, 101 | ) 102 | 103 | dd = as_dofdesc("vol") 104 | if use_quad: 105 | dd = dd.with_discr_tag(DISCR_TAG_QUAD) 106 | 107 | mat = forward_metric_derivative_mat( 108 | actx, dcoll, dd, 109 | _use_geoderiv_connection=actx.supports_nonscalar_broadcasting 110 | ) @ ( 111 | inverse_metric_derivative_mat( 112 | actx, dcoll, dd, 113 | _use_geoderiv_connection=actx.supports_nonscalar_broadcasting)) 114 | 115 | for i in range(mesh.dim): 116 | for j in range(mesh.dim): 117 | tgt = 1 if i == j else 0 118 | 119 | err = actx.to_numpy(flat_norm(mat[i, j] - tgt, ord=np.inf)) 120 | logger.info("error[%d, %d]: %.5e", i, j, err) 121 | assert err < 1.0e-12, (i, j, err) 122 | 123 | # }}} 124 | 125 | 126 | # You can test individual routines by typing 127 | # $ python test_metrics.py 'test_routine()' 128 | 129 | if __name__ == "__main__": 130 | import sys 131 | if len(sys.argv) > 1: 132 | exec(sys.argv[1]) 133 | else: 134 | pytest.main([__file__]) 135 | 136 | # vim: fdm=marker 137 | -------------------------------------------------------------------------------- /doc/misc.rst: -------------------------------------------------------------------------------- 1 | .. _installation: 2 | 3 | Installation 4 | ============ 5 | 6 | Installing :mod:`grudge` 7 | ------------------------ 8 | 9 | This set of instructions is intended for 64-bit Linux computers. 10 | MacOS support is in the works. 11 | 12 | #. Make sure your system has the basics to build software. 13 | 14 | On Debian derivatives (Ubuntu and many more), 15 | installing ``build-essential`` should do the trick. 16 | 17 | Everywhere else, just making sure you have the ``g++`` package should be 18 | enough. 19 | 20 | #. Install `miniforge `_:: 21 | 22 | curl -L -O https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh 23 | # then run 24 | bash ./Miniforge3-*.sh 25 | 26 | #. ``export CONDA=/WHERE/YOU/INSTALLED/miniforge3`` 27 | 28 | If you accepted the default location, this should work: 29 | 30 | ``export CONDA=$HOME/miniforge3`` 31 | 32 | #. ``$CONDA/bin/conda create -n dgfem`` 33 | 34 | #. ``source $CONDA/bin/activate dgfem`` 35 | 36 | #. ``conda install git pip pocl islpy pyopencl`` 37 | 38 | #. Type the following command:: 39 | 40 | hash -r; for i in pymbolic cgen genpy modepy pyvisfile loopy arraycontext meshmode grudge; do python -m pip install --editable "git+https://github.com/inducer/$i.git#egg=$i"; done 41 | 42 | .. note:: 43 | 44 | In each case, you may leave out the ``--editable`` flag if you would not like 45 | an editable copy of the source code checked out in a subfolder. 46 | 47 | Next time you want to use `grudge`, just run the following command:: 48 | 49 | source /WHERE/YOU/INSTALLED/miniforge3/bin/activate dgfem 50 | 51 | You may also like to add this to a startup file (like :file:`$HOME/.bashrc`) or create an alias for it. 52 | 53 | After this, you should be able to run the `tests `_ 54 | or `examples `_. 55 | 56 | Troubleshooting the Installation 57 | -------------------------------- 58 | 59 | /usr/bin/ld: cannot find -lstdc++ 60 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 61 | 62 | Try:: 63 | 64 | sudo apt-get install libstdc++-6-dev 65 | 66 | to install the missing C++ development package. 67 | 68 | No CL platforms found/unknown error -1001 69 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 70 | If you get:: 71 | 72 | pyopencl.cffi_cl.LogicError: clGetPlatformIDs failed: 73 | 74 | try:: 75 | 76 | conda update ocl-icd pocl 77 | 78 | (This indicates that the OpenCL driver loader didn't find any drivers, or the 79 | drivers were themselves missing dependencies.) 80 | 81 | Assertion 'error == 0' 82 | ~~~~~~~~~~~~~~~~~~~~~~~ 83 | 84 | If you get:: 85 | 86 | /opt/conda/conda-bld/home_1484016200338/work/pocl-0.13/lib/CL/devices/common.c:108: 87 | llvm_codegen: Assertion 'error == 0 ' failed. Aborted (core dumped) 88 | 89 | then you're likely out of memory. 90 | 91 | User-visible Changes 92 | ==================== 93 | 94 | Version 2016.1 95 | -------------- 96 | 97 | .. note:: 98 | 99 | This version is currently under development. You can get snapshots from 100 | grudge's `git repository `_ 101 | 102 | Licensing 103 | ========= 104 | 105 | :mod:`grudge` is licensed to you under the MIT/X Consortium license: 106 | 107 | Copyright (c) 2014-21 Andreas Klöckner and Contributors. 108 | 109 | Permission is hereby granted, free of charge, to any person 110 | obtaining a copy of this software and associated documentation 111 | files (the "Software"), to deal in the Software without 112 | restriction, including without limitation the rights to use, 113 | copy, modify, merge, publish, distribute, sublicense, and/or sell 114 | copies of the Software, and to permit persons to whom the 115 | Software is furnished to do so, subject to the following 116 | conditions: 117 | 118 | The above copyright notice and this permission notice shall be 119 | included in all copies or substantial portions of the Software. 120 | 121 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 122 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 123 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 124 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 125 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 126 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 127 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 128 | OTHER DEALINGS IN THE SOFTWARE. 129 | 130 | Acknowledgments 131 | =============== 132 | 133 | Work on grudge was supported in part by 134 | 135 | * the Department of Energy, National Nuclear Security Administration, 136 | under Award Number DE-NA0003963, 137 | * the US Navy ONR, under grant number N00014-14-1-0117, and 138 | * the US National Science Foundation under grant numbers CCF-1524433, 139 | and OAC-1931577. 140 | 141 | AK also gratefully acknowledges a hardware gift from Nvidia Corporation. 142 | 143 | The views and opinions expressed herein do not necessarily reflect those of the 144 | funding agencies. 145 | -------------------------------------------------------------------------------- /test/mesh_data.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from abc import ABC, abstractmethod 4 | from typing import TYPE_CHECKING, ClassVar 5 | 6 | import numpy as np 7 | from typing_extensions import override 8 | 9 | import meshmode.mesh.generation as mgen 10 | from meshmode.mesh.io import read_gmsh 11 | 12 | 13 | if TYPE_CHECKING: 14 | from collections.abc import Hashable, Sequence 15 | 16 | from meshmode.mesh import Mesh 17 | 18 | 19 | class MeshBuilder(ABC): 20 | resolutions: ClassVar[Sequence[Hashable]] 21 | ambient_dim: ClassVar[int] 22 | 23 | @abstractmethod 24 | def get_mesh( 25 | self, 26 | resolution: Hashable, 27 | mesh_order: int | None = None 28 | ) -> Mesh: 29 | ... 30 | 31 | 32 | class _GmshMeshBuilder(MeshBuilder): 33 | resolutions: ClassVar[Sequence[Hashable]] = [None] 34 | 35 | def __init__(self, filename: str) -> None: 36 | self._mesh_fn: str = filename 37 | 38 | @override 39 | def get_mesh(self, resolution, mesh_order=None) -> Mesh: 40 | assert resolution is None 41 | assert mesh_order is None 42 | return read_gmsh(self._mesh_fn, force_ambient_dim=self.ambient_dim) 43 | 44 | 45 | class GmshMeshBuilder2D(_GmshMeshBuilder): 46 | ambient_dim: ClassVar[int] = 2 47 | 48 | 49 | class GmshMeshBuilder3D(_GmshMeshBuilder): 50 | ambient_dim: ClassVar[int] = 3 51 | 52 | 53 | class Curve2DMeshBuilder(MeshBuilder): 54 | ambient_dim: ClassVar[int] = 2 55 | resolutions: ClassVar[Sequence[Hashable]] = [16, 32, 64, 128] 56 | 57 | @override 58 | def get_mesh(self, resolution, mesh_order=None): 59 | if mesh_order is None: 60 | mesh_order = 4 61 | return mgen.make_curve_mesh( 62 | self.curve_fn, # pylint: disable=no-member 63 | np.linspace(0.0, 1.0, resolution + 1), 64 | mesh_order) 65 | 66 | 67 | class EllipseMeshBuilder(Curve2DMeshBuilder): 68 | def __init__(self, radius=3.1, aspect_ratio: float = 2): 69 | self.radius: float = radius 70 | self.aspect_ratio: float = aspect_ratio 71 | 72 | @property 73 | def curve_fn(self): 74 | return lambda t: self.radius * mgen.ellipse(self.aspect_ratio, t) 75 | 76 | 77 | class StarfishMeshBuilder(Curve2DMeshBuilder): 78 | narms: ClassVar[int] = 5 79 | amplitude: ClassVar[float] = 0.25 80 | 81 | @property 82 | def curve_fn(self): 83 | return mgen.NArmedStarfish(self.narms, self.amplitude) 84 | 85 | 86 | class SphereMeshBuilder(MeshBuilder): 87 | ambient_dim: ClassVar[int] = 3 88 | 89 | resolutions: ClassVar[Sequence[Hashable]] = [0, 1, 2, 3] 90 | 91 | radius: float 92 | 93 | def __init__(self, radius: float = 1): 94 | self.radius = radius 95 | 96 | @override 97 | def get_mesh(self, resolution, mesh_order=4): 98 | from meshmode.mesh.generation import generate_sphere 99 | return generate_sphere(self.radius, order=mesh_order, 100 | uniform_refinement_rounds=resolution) 101 | 102 | 103 | class SpheroidMeshBuilder(MeshBuilder): 104 | ambient_dim: ClassVar[int] = 3 105 | 106 | resolutions: ClassVar[Sequence[Hashable]] = [0, 1, 2, 3] 107 | 108 | radius: float 109 | aspect_ratio: float 110 | 111 | def __init__(self, radius: float = 1, aspect_ratio: float = 2): 112 | self.radius = radius 113 | self.aspect_ratio = aspect_ratio 114 | 115 | @override 116 | def get_mesh(self, resolution, mesh_order=4): 117 | from meshmode.mesh.generation import generate_sphere 118 | mesh = generate_sphere(self.radius, order=mesh_order, 119 | uniform_refinement_rounds=resolution) 120 | 121 | from meshmode.mesh.processing import affine_map 122 | return affine_map(mesh, A=np.diag([1.0, 1.0, self.aspect_ratio])) 123 | 124 | 125 | class _BoxMeshBuilderBase(MeshBuilder): 126 | resolutions: ClassVar[Sequence[Hashable]] = [4, 8, 16] 127 | mesh_order: ClassVar[int] = 1 128 | 129 | a: ClassVar[tuple[float, ...]] = (-0.5, -0.5, -0.5) 130 | b: ClassVar[tuple[float, ...]] = (+0.5, +0.5, +0.5) 131 | 132 | @override 133 | def get_mesh(self, resolution, mesh_order=4): 134 | if not isinstance(resolution, list | tuple): 135 | resolution = (resolution,) * self.ambient_dim 136 | 137 | return mgen.generate_regular_rect_mesh( 138 | a=self.a, b=self.b, 139 | nelements_per_axis=resolution, 140 | order=mesh_order) 141 | 142 | 143 | class BoxMeshBuilder1D(_BoxMeshBuilderBase): 144 | ambient_dim: ClassVar[int] = 1 145 | 146 | 147 | class BoxMeshBuilder2D(_BoxMeshBuilderBase): 148 | ambient_dim: ClassVar[int] = 2 149 | 150 | 151 | class BoxMeshBuilder3D(_BoxMeshBuilderBase): 152 | ambient_dim: ClassVar[int] = 2 153 | 154 | 155 | class WarpedRectMeshBuilder(MeshBuilder): 156 | resolutions: ClassVar[Sequence[Hashable]] = [4, 6, 8] 157 | 158 | def __init__(self, dim): 159 | self.dim: int = dim 160 | 161 | @override 162 | def get_mesh(self, resolution, mesh_order=4): 163 | return mgen.generate_warped_rect_mesh( 164 | dim=self.dim, order=mesh_order, nelements_side=resolution) 165 | -------------------------------------------------------------------------------- /test/test_euler_model.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | 4 | __copyright__ = """ 5 | Copyright (C) 2021 University of Illinois Board of Trustees 6 | """ 7 | 8 | __license__ = """ 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in 17 | all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | THE SOFTWARE. 26 | """ 27 | 28 | import logging 29 | 30 | import pytest 31 | 32 | from arraycontext import ( 33 | ArrayContextFactory, 34 | pytest_generate_tests_for_array_contexts, 35 | ) 36 | 37 | from grudge import op 38 | from grudge.array_context import PytestPyOpenCLArrayContextFactory 39 | 40 | 41 | logger = logging.getLogger(__name__) 42 | pytest_generate_tests = pytest_generate_tests_for_array_contexts( 43 | [PytestPyOpenCLArrayContextFactory]) 44 | 45 | 46 | @pytest.mark.parametrize("order", [1, 2, 3]) 47 | def test_euler_vortex_convergence(actx_factory: ArrayContextFactory, order): 48 | 49 | from meshmode.discretization.poly_element import ( 50 | QuadratureSimplexGroupFactory, 51 | default_simplex_group_factory, 52 | ) 53 | from meshmode.mesh.generation import generate_regular_rect_mesh 54 | from pytools.convergence import EOCRecorder 55 | 56 | from grudge.discretization import make_discretization_collection 57 | from grudge.dof_desc import DISCR_TAG_BASE, DISCR_TAG_QUAD 58 | from grudge.dt_utils import h_max_from_volume 59 | from grudge.models.euler import EulerOperator, vortex_initial_condition 60 | from grudge.shortcuts import rk4_step 61 | 62 | actx = actx_factory() 63 | eoc_rec = EOCRecorder() 64 | quad_tag = DISCR_TAG_QUAD 65 | 66 | for resolution in [8, 16, 32]: 67 | 68 | # {{{ discretization 69 | 70 | mesh = generate_regular_rect_mesh( 71 | a=(0, -5), 72 | b=(20, 5), 73 | nelements_per_axis=(2*resolution, resolution), 74 | periodic=(True, True)) 75 | 76 | discr_tag_to_group_factory = { 77 | DISCR_TAG_BASE: default_simplex_group_factory(base_dim=2, order=order), 78 | DISCR_TAG_QUAD: QuadratureSimplexGroupFactory(2*order) 79 | } 80 | 81 | dcoll = make_discretization_collection( 82 | actx, mesh, 83 | discr_tag_to_group_factory=discr_tag_to_group_factory 84 | ) 85 | h_max = actx.to_numpy(h_max_from_volume(dcoll, dim=dcoll.ambient_dim)) 86 | nodes = actx.thaw(dcoll.nodes()) 87 | 88 | # }}} 89 | 90 | euler_operator = EulerOperator( 91 | dcoll, 92 | flux_type="lf", 93 | gamma=1.4, 94 | quadrature_tag=quad_tag 95 | ) 96 | 97 | def rhs(t, q, euler_operator=euler_operator): 98 | return euler_operator.operator(actx, t, q) 99 | 100 | compiled_rhs = actx.compile(rhs) 101 | 102 | fields = vortex_initial_condition(nodes) 103 | 104 | from grudge.dt_utils import h_min_from_volume 105 | 106 | cfl = 0.125 107 | cn = 0.5*(order + 1)**2 108 | dt = cfl * actx.to_numpy(h_min_from_volume(dcoll)) / cn 109 | final_time = dt * 10 110 | 111 | logger.info("Timestep size: %g", dt) 112 | 113 | # {{{ time stepping 114 | 115 | step = 0 116 | t = 0.0 117 | last_q = None 118 | while t < final_time: 119 | fields = actx.thaw(actx.freeze(fields)) 120 | fields = rk4_step(fields, t, dt, compiled_rhs) 121 | t += dt 122 | logger.info("[%04d] t = %.5f", step, t) 123 | last_q = fields 124 | last_t = t 125 | step += 1 126 | 127 | # }}} 128 | 129 | error_l2 = op.norm( 130 | dcoll, 131 | last_q - vortex_initial_condition(nodes, t=last_t), 132 | 2 133 | ) 134 | error_l2 = actx.to_numpy(error_l2) 135 | logger.info("h_max %.5e error %.5e", h_max, error_l2) 136 | eoc_rec.add_data_point(h_max, error_l2) 137 | 138 | logger.info("\n%s", eoc_rec.pretty_print(abscissa_label="h", 139 | error_label="L2 Error")) 140 | assert ( 141 | eoc_rec.order_estimate() >= order + 0.5 142 | ) 143 | 144 | 145 | # You can test individual routines by typing 146 | # $ python test_grudge.py 'test_routine()' 147 | 148 | if __name__ == "__main__": 149 | import sys 150 | if len(sys.argv) > 1: 151 | exec(sys.argv[1]) 152 | else: 153 | pytest.main([__file__]) 154 | 155 | # vim: fdm=marker 156 | -------------------------------------------------------------------------------- /test/test_modal_connections.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | 4 | __copyright__ = "Copyright (C) 2021 University of Illinois Board of Trustees" 5 | 6 | __license__ = """ 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | """ 25 | 26 | 27 | from arraycontext import ArrayContextFactory, pytest_generate_tests_for_array_contexts 28 | 29 | from grudge.array_context import PytestPyOpenCLArrayContextFactory 30 | from grudge.discretization import make_discretization_collection 31 | 32 | 33 | pytest_generate_tests = pytest_generate_tests_for_array_contexts( 34 | [PytestPyOpenCLArrayContextFactory]) 35 | 36 | import pytest 37 | 38 | import meshmode.mesh.generation as mgen 39 | from meshmode.discretization.poly_element import ( 40 | # Simplex group factories 41 | InterpolatoryQuadratureSimplexGroupFactory, 42 | # Tensor product group factories 43 | LegendreGaussLobattoTensorProductGroupFactory, 44 | ModalGroupFactory, 45 | PolynomialEquidistantSimplexGroupFactory, 46 | PolynomialWarpAndBlend2DRestrictingGroupFactory, 47 | # Quadrature-based (non-interpolatory) group factories 48 | QuadratureSimplexGroupFactory, 49 | ) 50 | from meshmode.dof_array import flat_norm 51 | 52 | import grudge.dof_desc as dof_desc 53 | 54 | 55 | @pytest.mark.parametrize("nodal_group_factory", [ 56 | InterpolatoryQuadratureSimplexGroupFactory, 57 | PolynomialWarpAndBlend2DRestrictingGroupFactory, 58 | PolynomialEquidistantSimplexGroupFactory, 59 | LegendreGaussLobattoTensorProductGroupFactory, 60 | ] 61 | ) 62 | def test_inverse_modal_connections( 63 | actx_factory: ArrayContextFactory, 64 | nodal_group_factory): 65 | actx = actx_factory() 66 | order = 4 67 | 68 | def f(x): 69 | return 2*actx.np.sin(20*x) + 0.5*actx.np.cos(10*x) 70 | 71 | # Make a regular rectangle mesh 72 | mesh = mgen.generate_regular_rect_mesh( 73 | a=(0, 0), b=(5, 3), npoints_per_axis=(10, 6), order=order, 74 | group_cls=nodal_group_factory.mesh_group_class 75 | ) 76 | 77 | dcoll = make_discretization_collection( 78 | actx, mesh, 79 | discr_tag_to_group_factory={ 80 | dof_desc.DISCR_TAG_BASE: nodal_group_factory(order), 81 | dof_desc.DISCR_TAG_MODAL: ModalGroupFactory(order), 82 | } 83 | ) 84 | 85 | dd_modal = dof_desc.DD_VOLUME_ALL_MODAL 86 | dd_volume = dof_desc.DD_VOLUME_ALL 87 | 88 | x_nodal = actx.thaw(dcoll.discr_from_dd(dd_volume).nodes()[0]) 89 | nodal_f = f(x_nodal) 90 | 91 | # Map nodal coefficients of f to modal coefficients 92 | forward_conn = dcoll.connection_from_dds(dd_volume, dd_modal) 93 | modal_f = forward_conn(nodal_f) 94 | # Now map the modal coefficients back to nodal 95 | backward_conn = dcoll.connection_from_dds(dd_modal, dd_volume) 96 | nodal_f_2 = backward_conn(modal_f) 97 | 98 | # This error should be small since we composed a map with 99 | # its inverse 100 | err = flat_norm(nodal_f - nodal_f_2) 101 | 102 | assert err <= 1e-13 103 | 104 | 105 | def test_inverse_modal_connections_quadgrid(actx_factory: ArrayContextFactory): 106 | actx = actx_factory() 107 | order = 5 108 | 109 | def f(x): 110 | return 1 + 2*x + 3*x**2 111 | 112 | # Make a regular rectangle mesh 113 | mesh = mgen.generate_regular_rect_mesh( 114 | a=(0, 0), b=(5, 3), npoints_per_axis=(10, 6), order=order, 115 | group_cls=QuadratureSimplexGroupFactory.mesh_group_class 116 | ) 117 | 118 | dcoll = make_discretization_collection( 119 | actx, mesh, 120 | discr_tag_to_group_factory={ 121 | dof_desc.DISCR_TAG_BASE: 122 | PolynomialWarpAndBlend2DRestrictingGroupFactory(order), 123 | dof_desc.DISCR_TAG_QUAD: QuadratureSimplexGroupFactory(2*order), 124 | dof_desc.DISCR_TAG_MODAL: ModalGroupFactory(order), 125 | } 126 | ) 127 | 128 | # Use dof descriptors on the quadrature grid 129 | dd_modal = dof_desc.DD_VOLUME_ALL_MODAL 130 | dd_quad = dof_desc.DOFDesc(dof_desc.DTAG_VOLUME_ALL, 131 | dof_desc.DISCR_TAG_QUAD) 132 | 133 | x_quad = actx.thaw(dcoll.discr_from_dd(dd_quad).nodes()[0]) 134 | quad_f = f(x_quad) 135 | 136 | # Map nodal coefficients of f to modal coefficients 137 | forward_conn = dcoll.connection_from_dds(dd_quad, dd_modal) 138 | modal_f = forward_conn(quad_f) 139 | # Now map the modal coefficients back to nodal 140 | backward_conn = dcoll.connection_from_dds(dd_modal, dd_quad) 141 | quad_f_2 = backward_conn(modal_f) 142 | 143 | # This error should be small since we composed a map with 144 | # its inverse 145 | err = flat_norm(quad_f - quad_f_2) 146 | 147 | assert err <= 1e-11 148 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "grudge" 7 | version = "2024.0" 8 | description = "Discretize discontinuous Galerkin operators quickly on heterogeneous hardware" 9 | readme = "README.rst" 10 | license = "MIT" 11 | authors = [ 12 | { name = "Andreas Kloeckner", email = "inform@tiker.net" }, 13 | ] 14 | requires-python = ">=3.10" 15 | classifiers = [ 16 | "Development Status :: 3 - Alpha", 17 | "Intended Audience :: Developers", 18 | "Intended Audience :: Other Audience", 19 | "Intended Audience :: Science/Research", 20 | "Natural Language :: English", 21 | "Programming Language :: Python", 22 | "Programming Language :: Python :: 3", 23 | "Topic :: Scientific/Engineering", 24 | "Topic :: Scientific/Engineering :: Information Analysis", 25 | "Topic :: Scientific/Engineering :: Mathematics", 26 | "Topic :: Scientific/Engineering :: Visualization", 27 | "Topic :: Software Development :: Libraries", 28 | "Topic :: Utilities", 29 | ] 30 | dependencies = [ 31 | "arraycontext>=2021.1", 32 | "constantdict", 33 | "loopy>=2024.1", 34 | "meshmode>=2021.2", 35 | "modepy>=2021.1", 36 | "pymbolic>=2022.2", 37 | "pyopencl>=2022.1", 38 | "pytools>=2024.1.18", 39 | ] 40 | 41 | [project.optional-dependencies] 42 | all = [ 43 | "meshpy>=2022.1", 44 | "mpi4py", 45 | "pymetis>=2023.1", 46 | "pytato>=2021.1", 47 | "pyvisfile>=2022.1", 48 | ] 49 | doc = [ 50 | "furo", 51 | "sphinx-copybutton", 52 | "sphinx>=4", 53 | ] 54 | test = [ 55 | "mypy", 56 | "pytest", 57 | "ruff" 58 | ] 59 | 60 | [project.urls] 61 | Documentation = "https://documen.tician.de/grudge" 62 | Homepage = "https://github.com/inducer/grudge" 63 | 64 | [tool.pytest.ini_options] 65 | markers = [ 66 | "mpi: mark a test using MPI", 67 | ] 68 | 69 | [tool.ruff] 70 | preview = true 71 | 72 | [tool.ruff.lint] 73 | extend-select = [ 74 | "B", # flake8-bugbear 75 | "C", # flake8-comprehensions 76 | "E", # pycodestyle 77 | "F", # pyflakes 78 | "G", # flake8-logging-format 79 | "I", # flake8-isort 80 | "N", # pep8-naming 81 | "NPY", # numpy 82 | "Q", # flake8-quotes 83 | "RUF", # ruff 84 | "UP", # pyupgrade 85 | "W", # pycodestyle 86 | "TC", 87 | ] 88 | extend-ignore = [ 89 | "C90", # McCabe complexity 90 | "E221", # multiple spaces before operator 91 | "E226", # missing whitespace around arithmetic operator 92 | "E241", # multiple spaces after comma 93 | "E242", # tab after comma 94 | "E402", # module level import not at the top of file 95 | ] 96 | 97 | [tool.ruff.lint.flake8-quotes] 98 | docstring-quotes = "double" 99 | inline-quotes = "double" 100 | multiline-quotes = "double" 101 | 102 | [tool.ruff.lint.isort] 103 | combine-as-imports = true 104 | known-first-party = [ 105 | "pytools", 106 | "pymbolic", 107 | "meshmode", 108 | "modepy", 109 | "pyopencl", 110 | "loopy", 111 | "arraycontext", 112 | ] 113 | known-local-folder = [ 114 | "grudge", 115 | ] 116 | lines-after-imports = 2 117 | required-imports = ["from __future__ import annotations"] 118 | 119 | [tool.ruff.lint.per-file-ignores] 120 | "examples/*.py" = ["I002"] 121 | "doc/conf.py" = ["I002"] 122 | 123 | [tool.typos.default] 124 | extend-ignore-re = [ 125 | "(?Rm)^.*(#|//)\\s*spellchecker:\\s*disable-line$" 126 | ] 127 | 128 | [tool.typos.default.extend-words] 129 | # like n-dimensional 130 | nd = "nd" 131 | # like 'theorem' 132 | thm = "thm" 133 | # used in `contrib/notes/hedge-notes.tm` 134 | stoopid = "stoopid" 135 | 136 | [tool.typos.files] 137 | extend-exclude = [ 138 | ] 139 | 140 | [tool.basedpyright] 141 | reportImplicitStringConcatenation = "none" 142 | reportUnnecessaryIsInstance = "none" 143 | reportUnusedCallResult = "none" 144 | reportExplicitAny = "none" 145 | reportUnusedParameter = "hint" 146 | 147 | reportPrivateUsage = "none" 148 | reportAny = "none" 149 | reportUnreachable = "hint" 150 | 151 | # This reports even cycles that are qualified by 'if TYPE_CHECKING'. Not what 152 | # we care about at this moment. 153 | # https://github.com/microsoft/pyright/issues/746 154 | reportImportCycles = "none" 155 | 156 | exclude = [ 157 | "doc", 158 | ".conda-root", 159 | ] 160 | 161 | pythonVersion = "3.10" 162 | pythonPlatform = "All" 163 | 164 | 165 | [[tool.basedpyright.executionEnvironments]] 166 | root = "test" 167 | reportArgumentType = "hint" 168 | reportAttributeAccessIssue = "hint" 169 | reportMissingImports = "none" 170 | reportMissingParameterType = "none" 171 | reportMissingTypeStubs = "none" 172 | reportUnknownArgumentType = "none" 173 | reportUnknownMemberType = "hint" 174 | reportUnknownParameterType = "none" 175 | reportUnknownVariableType = "none" 176 | reportUnknownLambdaType = "hint" 177 | reportOperatorIssue = "hint" 178 | reportPossiblyUnboundVariable = "hint" 179 | reportPrivateUsage = "none" 180 | reportUnusedImport = "hint" 181 | reportIndexIssue = "hint" 182 | reportOptionalOperand = "hint" 183 | reportUnusedVariable = "hint" 184 | reportCallIssue = "hint" 185 | reportPrivateLocalImportUsage = "none" 186 | reportReturnType = "hint" 187 | 188 | [[tool.basedpyright.executionEnvironments]] 189 | root = "examples" 190 | reportArgumentType = "hint" 191 | reportAttributeAccessIssue = "hint" 192 | reportMissingImports = "none" 193 | reportMissingParameterType = "none" 194 | reportMissingTypeStubs = "none" 195 | reportUnknownArgumentType = "none" 196 | reportUnknownMemberType = "hint" 197 | reportUnknownParameterType = "none" 198 | reportUnknownVariableType = "none" 199 | reportUnknownLambdaType = "hint" 200 | reportOperatorIssue = "hint" 201 | reportPossiblyUnboundVariable = "hint" 202 | reportPrivateUsage = "none" 203 | reportUnusedImport = "hint" 204 | reportIndexIssue = "hint" 205 | 206 | -------------------------------------------------------------------------------- /examples/maxwell/cavities.py: -------------------------------------------------------------------------------- 1 | __copyright__ = """ 2 | Copyright (C) 2015 Andreas Kloeckner 3 | Copyright (C) 2021 University of Illinois Board of Trustees 4 | """ 5 | 6 | __license__ = """ 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | """ 25 | 26 | 27 | import logging 28 | 29 | import numpy as np 30 | 31 | import pyopencl as cl 32 | import pyopencl.tools as cl_tools 33 | 34 | from grudge import op 35 | from grudge.array_context import PyOpenCLArrayContext 36 | from grudge.discretization import make_discretization_collection 37 | from grudge.models.em import get_rectangular_cavity_mode 38 | from grudge.shortcuts import set_up_rk4 39 | 40 | 41 | logger = logging.getLogger(__name__) 42 | 43 | 44 | def main(ctx_factory, dim=3, order=4, visualize=False): 45 | cl_ctx = ctx_factory() 46 | queue = cl.CommandQueue(cl_ctx) 47 | 48 | allocator = cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)) 49 | actx = PyOpenCLArrayContext(queue, allocator=allocator) 50 | 51 | from meshmode.mesh.generation import generate_regular_rect_mesh 52 | mesh = generate_regular_rect_mesh( 53 | a=(0.0,)*dim, 54 | b=(1.0,)*dim, 55 | nelements_per_axis=(4,)*dim) 56 | 57 | dcoll = make_discretization_collection(actx, mesh, order=order) 58 | 59 | if 0: 60 | epsilon0 = 8.8541878176e-12 # C**2 / (N m**2) 61 | mu0 = 4*np.pi*1e-7 # N/A**2. 62 | epsilon = 1*epsilon0 63 | mu = 1*mu0 64 | else: 65 | epsilon = 1 66 | mu = 1 67 | 68 | from grudge.models.em import MaxwellOperator 69 | 70 | maxwell_operator = MaxwellOperator( 71 | dcoll, 72 | epsilon, 73 | mu, 74 | flux_type=0.5, 75 | dimensions=dim 76 | ) 77 | 78 | def cavity_mode(x, t=0): 79 | if dim == 3: 80 | return get_rectangular_cavity_mode(actx, x, t, 1, (1, 2, 2)) 81 | else: 82 | return get_rectangular_cavity_mode(actx, x, t, 1, (2, 3)) 83 | 84 | fields = cavity_mode(actx.thaw(dcoll.nodes()), t=0) 85 | 86 | maxwell_operator.check_bc_coverage(mesh) 87 | 88 | def rhs(t, w): 89 | return maxwell_operator.operator(t, w) 90 | 91 | dt = actx.to_numpy( 92 | maxwell_operator.estimate_rk4_timestep(actx, dcoll, fields=fields)) 93 | 94 | dt_stepper = set_up_rk4("w", dt, fields, rhs) 95 | 96 | target_steps = 60 97 | final_t = dt * target_steps 98 | nsteps = int(final_t/dt) + 1 99 | 100 | logger.info("dt = %g nsteps = %d", dt, nsteps) 101 | 102 | from grudge.shortcuts import make_visualizer 103 | vis = make_visualizer(dcoll) 104 | 105 | step = 0 106 | 107 | def norm(u): 108 | return op.norm(dcoll, u, 2) 109 | 110 | e, h = maxwell_operator.split_eh(fields) 111 | 112 | if visualize: 113 | vis.write_vtk_file( 114 | f"fld-cavities-{step:04d}.vtu", 115 | [ 116 | ("e", e), 117 | ("h", h), 118 | ] 119 | ) 120 | 121 | for event in dt_stepper.run(t_end=final_t): 122 | if isinstance(event, dt_stepper.StateComputed): 123 | assert event.component_id == "w" 124 | 125 | step += 1 126 | e, h = maxwell_operator.split_eh(event.state_component) 127 | 128 | norm_e0 = actx.to_numpy(norm(u=e[0])) 129 | norm_e1 = actx.to_numpy(norm(u=e[1])) 130 | norm_h0 = actx.to_numpy(norm(u=h[0])) 131 | norm_h1 = actx.to_numpy(norm(u=h[1])) 132 | 133 | logger.info( 134 | "[%04d] t = %.5f |e0| = %.5e, |e1| = %.5e, |h0| = %.5e, |h1| = %.5e", 135 | step, event.t, 136 | norm_e0, norm_e1, norm_h0, norm_h1 137 | ) 138 | 139 | if step % 10 == 0: 140 | if visualize: 141 | vis.write_vtk_file( 142 | f"fld-cavities-{step:04d}.vtu", 143 | [ 144 | ("e", e), 145 | ("h", h), 146 | ] 147 | ) 148 | 149 | # NOTE: These are here to ensure the solution is bounded for the 150 | # time interval specified 151 | assert norm_e0 < 0.5 152 | assert norm_e1 < 0.5 153 | assert norm_h0 < 0.5 154 | assert norm_h1 < 0.5 155 | 156 | 157 | if __name__ == "__main__": 158 | import argparse 159 | 160 | parser = argparse.ArgumentParser() 161 | parser.add_argument("--dim", default=3, type=int) 162 | parser.add_argument("--order", default=4, type=int) 163 | parser.add_argument("--visualize", action="store_true") 164 | args = parser.parse_args() 165 | 166 | logging.basicConfig(level=logging.INFO) 167 | main(cl.create_some_context, 168 | dim=args.dim, 169 | order=args.order, 170 | visualize=args.visualize) 171 | -------------------------------------------------------------------------------- /contrib/notes/ip-primal.tm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <\body> 6 | Variational formulation of Poisson with fluxes > 7 | and |^>>, solving for 8 | ,\>: 9 | 10 | <\eqnarray*> 11 | >\\\>||>u\\\+K>\\\,>>|>\\\v>||>vf+K>v|^>\\.>>>> 12 | 13 | 14 | Use 15 | 16 | <\equation*> 17 | K>v\\\=>[v]\{\}+>{v}[\], 18 | 19 | 20 | where > includes the interior faces, to convert to: 21 | 22 | <\eqnarray*> 23 | >\\\>||>u\\\+>[]\{\}+>{}[\],>>|>\\\v>||>vf+>[v]\{|^>}+>{v}[|^>].>>>> 24 | 25 | 26 | Now pick \\v> to facilitate 27 | conversion to a bilinear form. This turns the first equation into. 28 | 29 | <\eqnarray*> 30 | >\\\v>||>u\\(\v)+>[]\{\v}+>{}[\v].>>>> 31 | 32 | 33 | We can now equate the RHSs of this latter equation and the second equation 34 | above: 35 | 36 | <\equation*> 37 | >vf+>[v]\{|^>}+>{v}[|^>]=->u\\(\v)|\>+>[]\{\v}+>{}[\v]. 38 | 39 | 40 | Now use the integration-by-parts formula 41 | 42 | <\equation*> 43 | >\v\\+>v\\\=>[v]\{\}+>{v}[\] 44 | 45 | 46 | to eliminate the double derivative: 47 | 48 | <\equation*> 49 | A=>u\\(\v)=>\u\\v+>[u]\{\v}+>{u}[\v]> 50 | 51 | 52 | and obtain 53 | 54 | <\equation*> 55 | >vf+>[v]\{|^>}+>{v}[|^>]=>\u\\v->[u]\{\v}->{u}[\v]>+>[]\{\v}+>{}[\v] 56 | 57 | 58 | Regroup and reorder: 59 | 60 | <\equation*> 61 | >\u\\v+>{-u}[\v]+>[-u]\{\v}->[v]\{|^>}->{v}[|^>]=>vf. 62 | 63 | 64 | For IP, e.g. 65 | 66 | <\equation*> 67 | \,|^>\{\u}-|h>[u]. 68 | 69 | 70 | Substitute in: 71 | 72 | <\equation*> 73 | >\u\\v+>{}>-u}[\v]|\>+>[}>-u]\{\v}->[v]\{u}-|h>[u]}>->{v}[u}-|h>[u]>]=>vf 74 | 75 | 76 | and obtain: 77 | 78 | <\equation*> 79 | >\u\\v->[u]\{\v}->[v]\{\u}->[v]|h>[u]=>vf. 80 | 81 | 82 | 83 | <\initial> 84 | <\collection> 85 | 86 | 87 | -------------------------------------------------------------------------------- /contrib/maxima/euler.mac: -------------------------------------------------------------------------------- 1 | kill(all); 2 | 3 | load("myhelpers.mac"); 4 | 5 | assume(%gamma>1); 6 | 7 | d:2; 8 | usyms:[u,v,w]; 9 | coords:[x,y,z]; 10 | uvec:makelist(usyms[i], i, 1, d); 11 | rhouvec:rho*uvec; 12 | E_expr:p/(%gamma-1)+rho/2*uvec.uvec; 13 | 14 | p_solved:rhs(solve(E=E_expr,p)[1]); 15 | p_used: p; /* p or p_solved */ 16 | 17 | /* fluxes ------------------------------------------------------------------- */ 18 | vars:append([rho, E], rhouvec); 19 | depends(append([rho, E, p], uvec), [x,y,z,t]); 20 | 21 | rho_flux:rhouvec; 22 | E_flux:uvec*(E+p_used); 23 | rhou_flux:makelist(makelist( 24 | rhouvec[i]*uvec[j] + if i = j then p_used else 0, 25 | j, 1, d), i, 1, d); 26 | 27 | all_fluxes:makelist( 28 | vstack([rho_flux[i]], [E_flux[i]], rhou_flux[i]), 29 | i, 1, d); 30 | 31 | euler_eqns:makelist( 32 | -'diff(vars[nr],t)=sum('diff(all_fluxes[i][nr], coords[i]), i, 1, d), 33 | nr, 1, length(vars)); 34 | 35 | /* linearization ------------------------------------------------------------ */ 36 | u0vec:makelist(concat(usyms[i], 0), i, 1, d); 37 | duvec:makelist(concat('d, usyms[i]), i, 1, d); 38 | drhouvec:rho0*duvec+drho*u0vec; 39 | 40 | assume(%gamma>1); 41 | assume(p0>0); 42 | assume(rho0>0); 43 | 44 | zero_subst:append( 45 | [rho=rho0, p=p0], 46 | makelist(usyms[i] =concat(usyms[i], 0), i, 1, d)); 47 | lin_subst:append( 48 | [rho=rho0+drho, p=p0+dp], 49 | makelist(usyms[i] =concat(usyms[i], 0) + duvec[i], i, 1, d)); 50 | 51 | E0_expr:subst(zero_subst,E_expr); 52 | dE:subst(lin_subst,E_expr)-E0_expr; 53 | 54 | lin_subst:append(lin_subst, [E=E0+dE]); 55 | 56 | kill_second_order_terms(x):=block( 57 | for lv1 in all_lin_vars do 58 | for lv2 in all_lin_vars do 59 | block( 60 | x:ratsubst(0,lv1*lv2, x), 61 | for co in coords do 62 | x:ratsubst(0,diff(lv1, co)*lv2, x)), 63 | x); 64 | 65 | lin_vars:append([drho, dE], drhouvec); 66 | all_lin_vars:append(duvec, [drho, dp]); 67 | 68 | depends(all_lin_vars, [x,y,z,t]); 69 | 70 | lin_euler_eqns:kill_second_order_terms(makelist( 71 | /* ref solution is time-invariant*/ 72 | -diff(lin_vars[nr],t) 73 | =sum(diff(subst(lin_subst, all_fluxes[i][nr]), coords[i]), i, 1, d), 74 | nr, 1, length(vars))); 75 | 76 | lin_euler_eqns:ratsimp(lin_euler_eqns); 77 | 78 | /* convert to primitive variables */ 79 | ddrho_dt: rhs(solve(lin_euler_eqns[1],diff(drho,t))[1]); 80 | 81 | lin_euler_eqns_p:makelist(lin_euler_eqns[i], i, 1, length(lin_euler_eqns)); 82 | 83 | for i:2 thru d+2 do 84 | lin_euler_eqns_p[i]:subst(diff(drho,t)=ddrho_dt, lin_euler_eqns_p[i]), 85 | 86 | for i:1 thru d do 87 | lin_euler_eqns_p[2+i]:-solve(lin_euler_eqns_p[2+i], diff(duvec[i], t))[1]; 88 | dduvec_dt: makelist( 89 | rhs(solve(lin_euler_eqns_p[i+2],diff(duvec[i],t))[1]), 90 | i, 1, d); 91 | 92 | lin_euler_eqns_p[2]:subst(E0=E0_expr, lin_euler_eqns_p[2]); 93 | for i:1 thru d do 94 | lin_euler_eqns_p[2]:subst(diff(duvec[i],t)=dduvec_dt[i], lin_euler_eqns_p[2]); 95 | lin_euler_eqns_p[2]:-solve(lin_euler_eqns_p[2], diff(dp, t))[1]; 96 | 97 | lin_euler_eqns_p:kill_second_order_terms(lin_euler_eqns_p); 98 | 99 | /* matrix building ---------------------------------------------------------- */ 100 | prim_lin_vars:append([drho,dp],duvec); 101 | n:makelist(concat(n, coords[k]), k, 1, d); 102 | euler_mat:genmatrix( 103 | lambda([i,j], 104 | sum(n[k] 105 | *diff(rhs(lin_euler_eqns_p[i]), diff(prim_lin_vars[j], coords[k])), 106 | k, 1, d)), 107 | length(lin_vars), length(lin_vars)); 108 | 109 | /* diagonalize system ------------------------------------------------------- */ 110 | [euler_V, euler_D, euler_invV]:hypdiagonalize(euler_mat); 111 | 112 | rel_D:ratsimp(euler_D - n.u0vec*ident(d+2)); 113 | 114 | /* auxiliary variables, external and internal states ------------------------ */ 115 | c0:sqrt(%gamma*p0/rho0); 116 | 117 | euler_wm:makelist(concat(prim_lin_vars[i], m), i, 1, length(prim_lin_vars)); 118 | euler_wp:makelist(concat(prim_lin_vars[i], p), i, 1, length(prim_lin_vars)); 119 | 120 | euler_sminw:hypsimp(euler_invV.euler_wm); 121 | euler_spinw:hypsimp(euler_invV.euler_wp); 122 | 123 | dumvec:makelist(euler_wm[i+2], i, 1, d); 124 | dupvec:makelist(euler_wp[i+2], i, 1, d); 125 | 126 | /* subsonic outflow bc------------------------------------------------------- */ 127 | euler_outflowbdryspinw:makelist( 128 | /* outer state equals free-stream flow, about which we have linearized. 129 | Hence the linearized state variable, which is the difference to free-stream 130 | flow, is set to zero. */ 131 | /* 132 | = 0: convection: from interior 133 | > 0: supersonic outflow: from interior 134 | < 0: supersonic inflow: from exterior 135 | */ 136 | if rel_D[i,i] >= 0 then euler_sminw[i,1] else 0, 137 | i, 1, d+2); 138 | 139 | euler_woutflowbdry:fullhypsimp(euler_V.euler_outflowbdryspinw); 140 | 141 | euler_known_woutflowbdry:vstack([ 142 | drhom + n.dumvec*rho0/(2*c0) - dpm/(2*c0^2), 143 | c0*rho0*(n.dumvec)/2 + dpm/2], 144 | dumvec - n*(n.dumvec)/2 + dpm*n/(2*c0*rho0) 145 | ); 146 | 147 | euler_diff_woutflowbdry:hypsimp(euler_woutflowbdry-euler_known_woutflowbdry); 148 | assert(norm_2_squared(euler_diff_woutflowbdry)=0); 149 | 150 | /* subsonic inflow bc ------------------------------------------------------- */ 151 | euler_inflowbdryspinw:makelist( 152 | /* outer state equals free-stream flow, about which we have linearized. 153 | Hence the linearized state variable, which is the difference to free-stream 154 | flow, is set to zero. */ 155 | /* 156 | > 0: supersonic outflow: from interior 157 | = 0: convection: from exterior 158 | < 0: supersonic inflow: from exterior 159 | */ 160 | if rel_D[i,i] <= 0 then 0 else euler_sminw[i,1], 161 | i, 1, d+2); 162 | 163 | euler_winflowbdry:fullhypsimp(euler_V.euler_inflowbdryspinw); 164 | 165 | euler_known_winflowbdry:vstack([ 166 | n.dumvec*rho0/(2*c0) + dpm/(2*c0^2), 167 | c0*rho0*(n.dumvec)/2 + dpm/2], 168 | n*(n.dumvec)/2 + dpm*n/(2*c0*rho0) 169 | ); 170 | 171 | euler_diff_winflowbdry:hypsimp(euler_winflowbdry-euler_known_winflowbdry); 172 | assert(norm_2_squared(euler_diff_winflowbdry)=0); 173 | -------------------------------------------------------------------------------- /examples/wave/var-propagation-speed.py: -------------------------------------------------------------------------------- 1 | __copyright__ = """ 2 | Copyright (C) 2015 Andreas Kloeckner 3 | Copyright (C) 2021 University of Illinois Board of Trustees 4 | """ 5 | 6 | __license__ = """ 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | """ 25 | 26 | 27 | import logging 28 | 29 | import numpy as np 30 | 31 | import pyopencl as cl 32 | import pyopencl.tools as cl_tools 33 | import pytools.obj_array as obj_array 34 | 35 | from grudge import op 36 | from grudge.array_context import PyOpenCLArrayContext 37 | from grudge.discretization import make_discretization_collection 38 | from grudge.shortcuts import set_up_rk4 39 | 40 | 41 | logger = logging.getLogger(__name__) 42 | 43 | 44 | def main(ctx_factory, dim=2, order=4, visualize=False): 45 | cl_ctx = ctx_factory() 46 | queue = cl.CommandQueue(cl_ctx) 47 | 48 | allocator = cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)) 49 | actx = PyOpenCLArrayContext(queue, allocator=allocator) 50 | 51 | from meshmode.mesh.generation import generate_regular_rect_mesh 52 | mesh = generate_regular_rect_mesh( 53 | a=(-0.5,)*dim, 54 | b=(0.5,)*dim, 55 | nelements_per_axis=(20,)*dim) 56 | 57 | dcoll = make_discretization_collection(actx, mesh, order=order) 58 | 59 | def source_f(actx, dcoll, t=0): 60 | source_center = np.array([0.1, 0.22, 0.33])[:dcoll.dim] 61 | source_width = 0.05 62 | source_omega = 3 63 | nodes = actx.thaw(dcoll.nodes()) 64 | source_center_dist = obj_array.flat( 65 | [nodes[i] - source_center[i] for i in range(dcoll.dim)] 66 | ) 67 | return ( 68 | np.sin(source_omega*t) 69 | * actx.np.exp( 70 | -np.dot(source_center_dist, source_center_dist) 71 | / source_width**2 72 | ) 73 | ) 74 | 75 | x = actx.thaw(dcoll.nodes()) 76 | ones = dcoll.zeros(actx) + 1 77 | c = actx.np.where(np.dot(x, x) < 0.15, 0.1 * ones, 0.2 * ones) 78 | 79 | from meshmode.mesh import BTAG_ALL, BTAG_NONE 80 | 81 | from grudge.models.wave import VariableCoefficientWeakWaveOperator 82 | 83 | wave_op = VariableCoefficientWeakWaveOperator( 84 | dcoll, 85 | actx.freeze(c), 86 | source_f=source_f, 87 | dirichlet_tag=BTAG_NONE, 88 | neumann_tag=BTAG_NONE, 89 | radiation_tag=BTAG_ALL, 90 | flux_type="upwind" 91 | ) 92 | 93 | fields = obj_array.flat( 94 | dcoll.zeros(actx), 95 | [dcoll.zeros(actx) for i in range(dcoll.dim)] 96 | ) 97 | 98 | wave_op.check_bc_coverage(mesh) 99 | 100 | def rhs(t, w): 101 | return wave_op.operator(t, w) 102 | 103 | dt = actx.to_numpy( 104 | 2/3 * wave_op.estimate_rk4_timestep(actx, dcoll, fields=fields)) 105 | dt_stepper = set_up_rk4("w", dt, fields, rhs) 106 | 107 | final_t = 1 108 | nsteps = int(final_t/dt) + 1 109 | 110 | logger.info("dt=%g nsteps=%d", dt, nsteps) 111 | 112 | from grudge.shortcuts import make_visualizer 113 | vis = make_visualizer(dcoll) 114 | 115 | step = 0 116 | 117 | def norm(u): 118 | return op.norm(dcoll, u, 2) 119 | 120 | from time import time 121 | t_last_step = time() 122 | 123 | if visualize: 124 | u = fields[0] 125 | v = fields[1:] 126 | vis.write_vtk_file( 127 | f"fld-var-propagation-speed-{step:04d}.vtu", 128 | [ 129 | ("u", u), 130 | ("v", v), 131 | ("c", c), 132 | ] 133 | ) 134 | 135 | for event in dt_stepper.run(t_end=final_t): 136 | if isinstance(event, dt_stepper.StateComputed): 137 | assert event.component_id == "w" 138 | 139 | step += 1 140 | 141 | if step % 10 == 0: 142 | logger.info("step: %d t: %.8e L2: %.8e", 143 | step, time() - t_last_step, 144 | actx.to_numpy(norm(event.state_component[0]))) 145 | if visualize: 146 | vis.write_vtk_file( 147 | f"fld-var-propagation-speed-{step:04d}.vtu", 148 | [ 149 | ("u", event.state_component[0]), 150 | ("v", event.state_component[1:]), 151 | ("c", c), 152 | ] 153 | ) 154 | t_last_step = time() 155 | 156 | # NOTE: These are here to ensure the solution is bounded for the 157 | # time interval specified 158 | assert norm(u=event.state_component[0]) < 1 159 | 160 | 161 | if __name__ == "__main__": 162 | import argparse 163 | 164 | parser = argparse.ArgumentParser() 165 | parser.add_argument("--dim", default=2, type=int) 166 | parser.add_argument("--order", default=4, type=int) 167 | parser.add_argument("--visualize", action="store_true") 168 | args = parser.parse_args() 169 | 170 | logging.basicConfig(level=logging.INFO) 171 | main(cl.create_some_context, 172 | dim=args.dim, 173 | order=args.order, 174 | visualize=args.visualize) 175 | -------------------------------------------------------------------------------- /contrib/maxima/eclean.mac: -------------------------------------------------------------------------------- 1 | kill(all); 2 | 3 | load("ecleanbase.mac"); 4 | 5 | print(extract_diagonal(eclean_D)); 6 | 7 | /* ------------------------------------------------------------------------- */ 8 | /* flux */ 9 | /* ------------------------------------------------------------------------- */ 10 | 11 | eclean_Dinc:ratsubst(c,1/(sqrt(%epsilon)*sqrt(%mu)), eclean_D); 12 | eclean_sflux:hyp_upwind_flux([-%chi*c,-c,0,c,%chi*c], eclean_Dinc); 13 | 14 | /* 15 | print("e-clean system flux in terms of characteristic variables:"); 16 | print(eclean_sflux); 17 | */ 18 | 19 | /* FIXME: eclean_V should not depend on epsilon and mu, but it does 20 | For now, make cp and cm equal. */ 21 | 22 | eclean_sflux:subst( 23 | [cp=1/(sqrt(%epsilon)*sqrt(%mu)), 24 | cm=1/(sqrt(%epsilon)*sqrt(%mu)), 25 | %chip=%chi, 26 | %chim=%chi], 27 | eclean_sflux); 28 | 29 | eclean_wflux:fullhypsimp(eclean_V.ev(eclean_sflux, 30 | [sm=eclean_sminw,sp=eclean_spinw])); 31 | 32 | /* 33 | print("e-clean system weak flux in terms of physical variables:"); 34 | print(eclean_wflux); 35 | */ 36 | 37 | eclean_strongwflux:eclean_A.eclean_wm - eclean_wflux; 38 | 39 | print("e-clean system strong flux in terms of physical variables:"); 40 | print(eclean_strongwflux); 41 | 42 | eclean_maxstrongdiff:fullhypsimp( 43 | eclean_strongwflux 44 | -append(max_strongwflux,matrix([0])) 45 | ); 46 | 47 | /* 48 | print("e-clean additional strong-form flux wrt Maxwell system:"); 49 | print(eclean_maxstrongdiff); 50 | */ 51 | 52 | eclean_simple_maxstrongdiff:1/2*append( 53 | %chi*c*n*(%phi[m]-%phi[p] - n.(max_Em-max_Ep)), 54 | [0,0,0], 55 | [c*%chi*(-(%phi[m]-%phi[p]) + n.(max_Em-max_Ep))]); 56 | 57 | assert(norm_2_squared(hypsimp( 58 | eclean_maxstrongdiff 59 | - ev(eclean_simple_maxstrongdiff, [c=1/sqrt(%epsilon*%mu)])))=0); 60 | 61 | /* ------------------------------------------------------------------------- */ 62 | /* Radiation BCs */ 63 | /* ------------------------------------------------------------------------- */ 64 | 65 | eclean_radbdryspinw:makelist( 66 | if eclean_D[i,i] >= 0 then eclean_sminw[i,1] else 0, 67 | i, 1, length(eclean_D))$ 68 | 69 | /* 70 | print("Radiation boundary condition for E-divclean system:"); 71 | print(fullhypsimp(eclean_V.eclean_radbdryspinw)); 72 | */ 73 | 74 | /* ------------------------------------------------------------------------- */ 75 | /* Better PEC */ 76 | /* ------------------------------------------------------------------------- */ 77 | /* PEC means: n x E = 0. (no tangential field) 78 | 79 | For normal Maxwell PEC, you prescribe E+ = - E-, even though the 80 | normal component is wrong (it should be equal to the E- normal component). 81 | That doesn't matter because the Maxwell flux only looks at the tangential 82 | component. But here, it suddenly ends up mattering, so we need to prescribe 83 | the normal component correctly. 84 | */ 85 | 86 | eclean_simplepecwp:covect(append(max_pecbdrywp, [%phi[m]])); 87 | eclean_betterpecwp:covect(append( 88 | normalcomp(max_Em)-tangentialcomp(max_Em), max_Hm, [-%phi[m]])); 89 | eclean_betterpecsp:fullhypsimp( 90 | eclean_invV.ev(eclean_betterpecwp, 91 | [Em=eclean_Emins, Hm=eclean_Hmins, %phi[m]=eclean_phimins])); 92 | 93 | /* 94 | print("Better PEC condition in characteristic variables:"); 95 | print(eclean_betterpecsp); 96 | */ 97 | 98 | print("Better PEC condition in physical variables:"); 99 | print(eclean_betterpecwp); 100 | 101 | 102 | eclean_flux_at_betterpec:fullhypsimp(ev(append(max_strongwflux,matrix([0]))/*eclean_strongwflux*/, 103 | [Ep=subrange(eclean_betterpecwp,1,3), 104 | Hp=subrange(eclean_betterpecwp,4,6), 105 | %phi[p]=eclean_betterpecwp[7,1]])); 106 | 107 | eclean_flux_at_simplepec:fullhypsimp(ev(append(max_strongwflux,matrix([0]))/*eclean_strongwflux*/, 108 | [Ep=subrange(eclean_simplepecwp,1,3), 109 | Hp=subrange(eclean_simplepecwp,4,6), 110 | %phi[p]=eclean_simplepecwp[7,1]])); 111 | 112 | assert(norm_2_squared(hypsimp(eclean_flux_at_betterpec-eclean_flux_at_simplepec))=0); 113 | 114 | /* ------------------------------------------------------------------------- */ 115 | 116 | eclean_munzpecwithphi(phival):=fullhypsimp(eclean_invV. 117 | vstack(vstack(-eclean_Emins, eclean_Hmins), [phival])); 118 | 119 | /* 120 | print("Munz et al PEC boundary with phi+=0:"); 121 | print(eclean_munzpecwithphi(0)); 122 | print("Munz et al PEC boundary with phi+=phi-:"); 123 | print(eclean_munzpecwithphi(eclean_phimins)); 124 | print("Munz et al PEC boundary with phi+=-phi-:"); 125 | print(eclean_munzpecwithphi(-eclean_phimins)); 126 | */ 127 | 128 | /* ------------------------------------------------------------------------- */ 129 | /* chi-radiation + PEC BC */ 130 | /* ------------------------------------------------------------------------- */ 131 | eclean_chiradbdryspinw:[ 132 | -eclean_sminw[3,1], 133 | -eclean_sminw[4,1], 134 | -eclean_sminw[1,1], 135 | -eclean_sminw[2,1], 136 | 0*eclean_sminw[5,1], 137 | eclean_sminw[6,1], 138 | eclean_sminw[7,1] 139 | ]$ 140 | 141 | eclean_chiradwp:fullhypsimp(eclean_V.eclean_chiradbdryspinw); 142 | 143 | print("PEC+chirad BC for div E cleaning system"); 144 | print(eclean_chiradwp); 145 | 146 | eclean_simple_expr_chiradwp:append( 147 | -max_Em+3/2*n*(n.max_Em) + 1/2*%phi[m]*n, 148 | max_Hm, 149 | 1/2*[%phi[m]+max_Em.n] 150 | ); 151 | 152 | eclean_diff_chiradwp:hypsimp(eclean_chiradwp 153 | - subst([c=1/sqrt(%epsilon*%mu)], eclean_simple_expr_chiradwp)); 154 | 155 | assert(norm_2_squared(eclean_diff_chiradwp)=0); 156 | 157 | /* 158 | print("Limit of PEC+chirad BC as %phi, %chi -> 0"); 159 | print(fullhypsimp(limit(limit( 160 | subst([%phi[m]=phi],eclean_chiradwp),phi,0),%chi,0))); 161 | */ 162 | 163 | eclean_strongw_chirad_flux:fullhypsimp(ev(eclean_strongwflux, 164 | [Ep=subrange(eclean_chiradwp,1,3), 165 | Hp=subrange(eclean_chiradwp,4,6), 166 | %phi[p]=eclean_chiradwp[7,1]])); 167 | print("Flux at PEC+chirad:"); 168 | print(eclean_strongw_chirad_flux); 169 | 170 | eclean_strongw_pec_flux:fullhypsimp(ev(eclean_strongwflux, 171 | [Ep=subrange(eclean_betterpecwp,1,3), 172 | Hp=subrange(eclean_betterpecwp,4,6), 173 | %phi[p]=eclean_betterpecwp[7,1]])); 174 | print("Flux at pure PEC:"); 175 | print(eclean_strongw_pec_flux); 176 | 177 | eclean_strongw_flux_diff:fullhypsimp( 178 | eclean_strongw_pec_flux-eclean_strongw_chirad_flux); 179 | 180 | print("PEC Flux - Chirad Flux"); 181 | print(eclean_strongw_flux_diff); 182 | 183 | 184 | /* 185 | f1:subst([ 186 | Normal(0)=nx,Normal(1)=ny,Normal(2)=nz, 187 | Int[0]=Em[1],Int[1]=Em[2],Int[2]=Em[3], 188 | Ext[0]=Ep[1],Ext[1]=Ep[2],Ext[2]=Ep[3], 189 | Int[3]=Hm[1],Int[4]=Hm[2],Int[5]=Hm[3], 190 | Ext[3]=Hp[1],Ext[4]=Hp[2],Ext[5]=Hp[3], 191 | Int[6]=%phi[m],Ext[6]=%phi[p]] 192 | ,f0) 193 | */ 194 | -------------------------------------------------------------------------------- /examples/wave/wave-min-mpi.py: -------------------------------------------------------------------------------- 1 | __copyright__ = """ 2 | Copyright (C) 2015 Andreas Kloeckner 3 | Copyright (C) 2021 University of Illinois Board of Trustees 4 | """ 5 | 6 | __license__ = """ 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | """ 25 | 26 | 27 | import logging 28 | 29 | import numpy as np 30 | from mpi4py import MPI 31 | 32 | import pyopencl as cl 33 | import pyopencl.tools as cl_tools 34 | import pytools.obj_array as obj_array 35 | 36 | import grudge.op as op 37 | from grudge import make_discretization_collection 38 | from grudge.array_context import MPIPyOpenCLArrayContext 39 | from grudge.shortcuts import set_up_rk4 40 | 41 | 42 | logger = logging.getLogger(__name__) 43 | 44 | 45 | class WaveTag: 46 | pass 47 | 48 | 49 | def main(dim=2, order=4, visualize=True): 50 | comm = MPI.COMM_WORLD 51 | num_parts = comm.size 52 | 53 | cl_ctx = cl.create_some_context() 54 | queue = cl.CommandQueue(cl_ctx) 55 | 56 | allocator = cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)) 57 | actx = MPIPyOpenCLArrayContext(comm, queue, allocator=allocator) 58 | 59 | from meshmode.distributed import get_partition_by_pymetis, membership_list_to_map 60 | from meshmode.mesh.processing import partition_mesh 61 | 62 | if comm.rank == 0: 63 | from meshmode.mesh.generation import generate_regular_rect_mesh 64 | mesh = generate_regular_rect_mesh( 65 | a=(-0.5,)*dim, 66 | b=(0.5,)*dim, 67 | nelements_per_axis=(16,)*dim) 68 | 69 | logger.info("%d elements", mesh.nelements) 70 | 71 | part_id_to_part = partition_mesh(mesh, 72 | membership_list_to_map( 73 | get_partition_by_pymetis(mesh, num_parts))) 74 | parts = [part_id_to_part[i] for i in range(num_parts)] 75 | local_mesh = comm.scatter(parts) 76 | 77 | del mesh 78 | 79 | else: 80 | local_mesh = comm.scatter(None) 81 | 82 | dcoll = make_discretization_collection(actx, local_mesh, order=order) 83 | 84 | def source_f(actx, dcoll, t=0): 85 | source_center = np.array([0.1, 0.22, 0.33])[:dcoll.dim] 86 | source_width = 0.05 87 | source_omega = 3 88 | nodes = actx.thaw(dcoll.nodes()) 89 | source_center_dist = obj_array.flat( 90 | [nodes[i] - source_center[i] for i in range(dcoll.dim)] 91 | ) 92 | return ( 93 | np.sin(source_omega*t) 94 | * actx.np.exp( 95 | -np.dot(source_center_dist, source_center_dist) 96 | / source_width**2 97 | ) 98 | ) 99 | 100 | from meshmode.mesh import BTAG_ALL, BTAG_NONE 101 | 102 | from grudge.models.wave import WeakWaveOperator 103 | 104 | wave_op = WeakWaveOperator( 105 | dcoll, 106 | 0.1, 107 | source_f=source_f, 108 | dirichlet_tag=BTAG_NONE, 109 | neumann_tag=BTAG_NONE, 110 | radiation_tag=BTAG_ALL, 111 | flux_type="upwind" 112 | ) 113 | 114 | fields = obj_array.flat( 115 | dcoll.zeros(actx), 116 | [dcoll.zeros(actx) for i in range(dcoll.dim)] 117 | ) 118 | 119 | dt = actx.to_numpy( 120 | 2/3 * wave_op.estimate_rk4_timestep(actx, dcoll, fields=fields)) 121 | 122 | wave_op.check_bc_coverage(local_mesh) 123 | 124 | def rhs(t, w): 125 | return wave_op.operator(t, w) 126 | 127 | dt_stepper = set_up_rk4("w", dt, fields, rhs) 128 | 129 | final_t = 10 130 | nsteps = int(final_t/dt) + 1 131 | 132 | if comm.rank == 0: 133 | logger.info("dt=%g nsteps=%d", dt, nsteps) 134 | 135 | from grudge.shortcuts import make_visualizer 136 | vis = make_visualizer(dcoll) 137 | 138 | step = 0 139 | 140 | def norm(u): 141 | return op.norm(dcoll, u, 2) 142 | 143 | from time import time 144 | t_last_step = time() 145 | 146 | if visualize: 147 | u = fields[0] 148 | v = fields[1:] 149 | vis.write_parallel_vtk_file( 150 | comm, 151 | f"fld-wave-min-mpi-{{rank:03d}}-{step:04d}.vtu", 152 | [ 153 | ("u", u), 154 | ("v", v), 155 | ] 156 | ) 157 | 158 | for event in dt_stepper.run(t_end=final_t): 159 | if isinstance(event, dt_stepper.StateComputed): 160 | assert event.component_id == "w" 161 | 162 | step += 1 163 | l2norm = norm(u=event.state_component[0]) 164 | 165 | if step % 10 == 0: 166 | if comm.rank == 0: 167 | logger.info("step: %d t: %.8e L2: %.8e", 168 | step, time() - t_last_step, l2norm) 169 | if visualize: 170 | vis.write_parallel_vtk_file( 171 | comm, 172 | f"fld-wave-min-mpi-{{rank:03d}}-{step:04d}.vtu", 173 | [ 174 | ("u", event.state_component[0]), 175 | ("v", event.state_component[1:]), 176 | ] 177 | ) 178 | t_last_step = time() 179 | 180 | # NOTE: These are here to ensure the solution is bounded for the 181 | # time interval specified 182 | assert l2norm < 1 183 | 184 | 185 | if __name__ == "__main__": 186 | import argparse 187 | 188 | parser = argparse.ArgumentParser() 189 | parser.add_argument("--dim", default=2, type=int) 190 | parser.add_argument("--order", default=4, type=int) 191 | parser.add_argument("--visualize", action="store_true") 192 | args = parser.parse_args() 193 | 194 | logging.basicConfig(level=logging.INFO) 195 | main( 196 | dim=args.dim, 197 | order=args.order, 198 | visualize=args.visualize) 199 | -------------------------------------------------------------------------------- /contrib/notes/ip-primal-nu.tm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <\body> 6 | Variational formulation of Poisson with fluxes > 7 | and |^>>, solving for 8 | ,\>: 9 | 10 | <\eqnarray*> 11 | >\\\>||>u\\\+K>\\\,>>|>\\\v>||>vf+K>v|^>\\.>>>> 12 | 13 | 14 | Use 15 | 16 | <\equation*> 17 | K>v\\\=>[v]\{\}+>{v}[\], 18 | 19 | 20 | where > includes the interior faces, to convert to: 21 | 22 | <\eqnarray*> 23 | >\\\>||>u\\\+>[]\{\}+>{}[\],>>|>\\>\v>||>vf+>[v]\{|^>}+>{v}[|^>].>>>> 24 | 25 | 26 | Now pick \>\v> 27 | to facilitate conversion to a bilinear form. This turns the first equation 28 | into. 29 | 30 | <\eqnarray*> 31 | >\\\v>||>u\\(>\v)+>[]\{>\v}+>{}[>\v].>>>> 32 | 33 | 34 | We can now equate the RHSs of this latter equation and the second equation 35 | above: 36 | 37 | <\equation*> 38 | >vf+>[v]\{|^>}+>{v}[|^>]=->u\\(>\v)|\>+>[]\{>\v}+>{}[>\v]. 39 | 40 | 41 | Now use the integration-by-parts formula 42 | 43 | <\equation*> 44 | >\v\\+>v\\\=>[v]\{\}+>{v}[\] 45 | 46 | 47 | to eliminate the double derivative, with 48 | =\\v> and >. 49 | 50 | <\equation*> 51 | A=>u\\(>\v)=>(\u)\(>\v)+>[u]\{>\v}+>{u}[>\v]> 52 | 53 | 54 | and obtain 55 | 56 | <\equation*> 57 | >vf+>[v]\{|^>}+>{v}[|^>]=>>\u\\v->[u]\{>\v}->{u}[>\v]>+>[]\{>\v}+>{}[>\v] 58 | 59 | 60 | Regroup and reorder: 61 | 62 | <\equation*> 63 | >>\u\\v+>{-u}[>\v]+>[-u]\{>\v}->[v]\{|^>}->{v}[|^>]=>vf. 64 | 65 | 66 | For IP, e.g. 67 | 68 | <\equation*> 69 | \{u},|^>\{>\u}-|h>>[u]. 70 | 71 | 72 | Substitute in: 73 | 74 | <\equation*> 75 | >>\u\\v+>{}>-u}[>\v]|\>+>[}>-u]\{>\v}->[v]\{>\u}-|h>>[u]}>->{v}[>\u}-|h>>[u]>]=>vf 76 | 77 | 78 | and obtain: 79 | 80 | <\equation*> 81 | >>\u\\v->[u]\{>\v}->[v]\{>\u}->[v]|h>>[u]=>vf. 82 | 83 | 84 | 85 | <\initial> 86 | <\collection> 87 | 88 | 89 | -------------------------------------------------------------------------------- /examples/advection/weak.py: -------------------------------------------------------------------------------- 1 | __copyright__ = """ 2 | Copyright (C) 2007 Andreas Kloeckner 3 | Copyright (C) 2021 University of Illinois Board of Trustees 4 | """ 5 | 6 | __license__ = """ 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | """ 25 | 26 | import logging 27 | import os 28 | 29 | import numpy as np 30 | import numpy.linalg as la 31 | 32 | import pyopencl as cl 33 | import pyopencl.tools as cl_tools 34 | from arraycontext import flatten 35 | from meshmode.mesh import BTAG_ALL 36 | 37 | import grudge.dof_desc as dof_desc 38 | import grudge.op as op 39 | from grudge.array_context import PyOpenCLArrayContext 40 | 41 | 42 | logger = logging.getLogger(__name__) 43 | 44 | 45 | # {{{ plotting (keep in sync with `var-velocity.py`) 46 | 47 | class Plotter: 48 | def __init__(self, actx, dcoll, order, visualize=True, ylim=None): 49 | self.actx = actx 50 | self.dim = dcoll.ambient_dim 51 | 52 | self.visualize = visualize 53 | if not self.visualize: 54 | return 55 | 56 | if self.dim == 1: 57 | import matplotlib.pyplot as pt 58 | self.fig = pt.figure(figsize=(8, 8), dpi=300) 59 | self.ylim = ylim 60 | 61 | volume_discr = dcoll.discr_from_dd(dof_desc.DD_VOLUME_ALL) 62 | self.x = actx.to_numpy(flatten(volume_discr.nodes()[0], self.actx)) 63 | else: 64 | from grudge.shortcuts import make_visualizer 65 | self.vis = make_visualizer(dcoll) 66 | 67 | def __call__(self, evt, basename, overwrite=True): 68 | if not self.visualize: 69 | return 70 | 71 | if self.dim == 1: 72 | u = self.actx.to_numpy(flatten(evt.state_component, self.actx)) 73 | 74 | filename = f"{basename}.png" 75 | if not overwrite and os.path.exists(filename): 76 | from meshmode import FileExistsError 77 | raise FileExistsError(f"output file '{filename}' already exists") 78 | 79 | ax = self.fig.gca() 80 | ax.plot(self.x, u, "-") 81 | ax.plot(self.x, u, "k.") 82 | if self.ylim is not None: 83 | ax.set_ylim(self.ylim) 84 | 85 | ax.set_xlabel("$x$") 86 | ax.set_ylabel("$u$") 87 | ax.set_title(f"t = {evt.t:.2f}") 88 | 89 | self.fig.savefig(filename) 90 | self.fig.clf() 91 | else: 92 | self.vis.write_vtk_file(f"{basename}.vtu", [ 93 | ("u", evt.state_component) 94 | ], overwrite=overwrite) 95 | 96 | # }}} 97 | 98 | 99 | def main(ctx_factory, dim=2, order=4, visualize=False): 100 | cl_ctx = ctx_factory() 101 | queue = cl.CommandQueue(cl_ctx) 102 | 103 | allocator = cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)) 104 | actx = PyOpenCLArrayContext(queue, allocator=allocator) 105 | 106 | # {{{ parameters 107 | 108 | # domain [-d/2, d/2]^dim 109 | d = 1.0 110 | # number of points in each dimension 111 | npoints = 20 112 | 113 | # final time 114 | final_time = 1.0 115 | 116 | # velocity field 117 | c = np.array([0.5] * dim) 118 | norm_c = la.norm(c) 119 | 120 | # flux 121 | flux_type = "central" 122 | 123 | # }}} 124 | 125 | # {{{ discretization 126 | 127 | from meshmode.mesh.generation import generate_box_mesh 128 | mesh = generate_box_mesh( 129 | [np.linspace(-d/2, d/2, npoints) for _ in range(dim)], 130 | order=order) 131 | 132 | from grudge.discretization import make_discretization_collection 133 | 134 | dcoll = make_discretization_collection(actx, mesh, order=order) 135 | 136 | # }}} 137 | 138 | # {{{ weak advection operator 139 | 140 | def f(x): 141 | return actx.np.sin(3 * x) 142 | 143 | def u_analytic(x, t=0): 144 | return f(-np.dot(c, x) / norm_c + t * norm_c) 145 | 146 | from grudge.models.advection import WeakAdvectionOperator 147 | 148 | adv_operator = WeakAdvectionOperator( 149 | dcoll, 150 | c, 151 | inflow_u=lambda t: u_analytic( 152 | actx.thaw(dcoll.nodes(dd=BTAG_ALL)), 153 | t=t 154 | ), 155 | flux_type=flux_type 156 | ) 157 | 158 | nodes = actx.thaw(dcoll.nodes()) 159 | u = u_analytic(nodes, t=0) 160 | 161 | def rhs(t, u): 162 | return adv_operator.operator(t, u) 163 | 164 | dt = actx.to_numpy(adv_operator.estimate_rk4_timestep(actx, dcoll, fields=u)) 165 | 166 | logger.info("Timestep size: %g", dt) 167 | 168 | # }}} 169 | 170 | # {{{ time stepping 171 | 172 | from grudge.shortcuts import set_up_rk4 173 | dt_stepper = set_up_rk4("u", float(dt), u, rhs) 174 | plot = Plotter(actx, dcoll, order, visualize=visualize, 175 | ylim=[-1.1, 1.1]) 176 | 177 | step = 0 178 | norm_u = 0.0 179 | for event in dt_stepper.run(t_end=final_time): 180 | if not isinstance(event, dt_stepper.StateComputed): 181 | continue 182 | 183 | if step % 10 == 0: 184 | norm_u = actx.to_numpy(op.norm(dcoll, event.state_component, 2)) 185 | plot(event, f"fld-weak-{step:04d}") 186 | 187 | step += 1 188 | logger.info("[%04d] t = %.5f |u| = %.5e", step, event.t, norm_u) 189 | 190 | # NOTE: These are here to ensure the solution is bounded for the 191 | # time interval specified 192 | assert norm_u < 1 193 | 194 | # }}} 195 | 196 | 197 | if __name__ == "__main__": 198 | import argparse 199 | 200 | parser = argparse.ArgumentParser() 201 | parser.add_argument("--dim", default=2, type=int) 202 | parser.add_argument("--order", default=4, type=int) 203 | parser.add_argument("--visualize", action="store_true") 204 | args = parser.parse_args() 205 | 206 | logging.basicConfig(level=logging.INFO) 207 | main(cl.create_some_context, 208 | dim=args.dim, 209 | order=args.order, 210 | visualize=args.visualize) 211 | -------------------------------------------------------------------------------- /examples/euler/vortex.py: -------------------------------------------------------------------------------- 1 | __copyright__ = """ 2 | Copyright (C) 2021 University of Illinois Board of Trustees 3 | """ 4 | 5 | __license__ = """ 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | """ 24 | 25 | 26 | import logging 27 | 28 | import pyopencl as cl 29 | import pyopencl.tools as cl_tools 30 | 31 | import grudge.op as op 32 | from grudge.array_context import PyOpenCLArrayContext, PytatoPyOpenCLArrayContext 33 | from grudge.models.euler import EulerOperator, vortex_initial_condition 34 | from grudge.shortcuts import compiled_lsrk45_step 35 | 36 | 37 | logger = logging.getLogger(__name__) 38 | 39 | 40 | def run_vortex(actx, order=3, resolution=8, final_time=5, 41 | overintegration=False, 42 | flux_type="central", 43 | visualize=False): 44 | 45 | logger.info( 46 | """ 47 | Isentropic vortex parameters:\n 48 | order: %s\n 49 | final_time: %s\n 50 | resolution: %s\n 51 | overintegration: %s\n 52 | flux_type: %s\n 53 | visualize: %s 54 | """, 55 | order, final_time, resolution, 56 | overintegration, flux_type, visualize 57 | ) 58 | 59 | # eos-related parameters 60 | gamma = 1.4 61 | 62 | # {{{ discretization 63 | 64 | from meshmode.mesh.generation import generate_regular_rect_mesh 65 | 66 | mesh = generate_regular_rect_mesh( 67 | a=(0, -5), 68 | b=(20, 5), 69 | nelements_per_axis=(2*resolution, resolution), 70 | periodic=(True, True)) 71 | 72 | from meshmode.discretization.poly_element import ( 73 | QuadratureSimplexGroupFactory, 74 | default_simplex_group_factory, 75 | ) 76 | 77 | from grudge.discretization import make_discretization_collection 78 | from grudge.dof_desc import DISCR_TAG_BASE, DISCR_TAG_QUAD 79 | 80 | exp_name = f"fld-vortex-N{order}-K{resolution}-{flux_type}" 81 | 82 | if overintegration: 83 | exp_name += "-overintegrated" 84 | quad_tag = DISCR_TAG_QUAD 85 | else: 86 | quad_tag = None 87 | 88 | dcoll = make_discretization_collection( 89 | actx, mesh, 90 | discr_tag_to_group_factory={ 91 | DISCR_TAG_BASE: default_simplex_group_factory( 92 | base_dim=mesh.dim, order=order), 93 | DISCR_TAG_QUAD: QuadratureSimplexGroupFactory(2*order) 94 | } 95 | ) 96 | 97 | # }}} 98 | 99 | # {{{ Euler operator 100 | 101 | euler_operator = EulerOperator( 102 | dcoll, 103 | flux_type=flux_type, 104 | gamma=gamma, 105 | quadrature_tag=quad_tag 106 | ) 107 | 108 | def rhs(t, q): 109 | return euler_operator.operator(actx, t, q) 110 | 111 | compiled_rhs = actx.compile(rhs) 112 | 113 | fields = vortex_initial_condition(actx.thaw(dcoll.nodes())) 114 | 115 | from grudge.dt_utils import h_min_from_volume 116 | 117 | cfl = 0.01 118 | cn = 0.5*(order + 1)**2 119 | dt = cfl * actx.to_numpy(h_min_from_volume(dcoll)) / cn 120 | 121 | logger.info("Timestep size: %g", dt) 122 | 123 | # }}} 124 | 125 | from grudge.shortcuts import make_visualizer 126 | 127 | vis = make_visualizer(dcoll) 128 | 129 | fields = actx.freeze_thaw(fields) 130 | 131 | # {{{ time stepping 132 | 133 | step = 0 134 | t = 0.0 135 | while t < final_time: 136 | if step % 10 == 0: 137 | norm_q = actx.to_numpy(op.norm(dcoll, fields, 2)) 138 | logger.info("[%04d] t = %.5f |q| = %.5e", step, t, norm_q) 139 | 140 | if visualize: 141 | vis.write_vtk_file( 142 | f"{exp_name}-{step:04d}.vtu", 143 | [ 144 | ("rho", fields.mass), 145 | ("energy", fields.energy), 146 | ("momentum", fields.momentum) 147 | ] 148 | ) 149 | assert norm_q < 200 150 | 151 | fields = compiled_lsrk45_step(actx, fields, t, dt, compiled_rhs) 152 | t += dt 153 | step += 1 154 | 155 | # }}} 156 | 157 | 158 | def main(ctx_factory, order=3, final_time=5, resolution=8, 159 | overintegration=False, 160 | lf_stabilization=False, 161 | visualize=False, 162 | lazy=False): 163 | cl_ctx = ctx_factory() 164 | queue = cl.CommandQueue(cl_ctx) 165 | 166 | allocator = cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)) 167 | if lazy: 168 | actx = PytatoPyOpenCLArrayContext(queue, allocator=allocator) 169 | else: 170 | actx = PyOpenCLArrayContext(queue, allocator=allocator) 171 | 172 | if lf_stabilization: 173 | flux_type = "lf" 174 | else: 175 | flux_type = "central" 176 | 177 | run_vortex( 178 | actx, 179 | order=order, 180 | resolution=resolution, 181 | final_time=final_time, 182 | overintegration=overintegration, 183 | flux_type=flux_type, 184 | visualize=visualize 185 | ) 186 | 187 | 188 | if __name__ == "__main__": 189 | import argparse 190 | 191 | parser = argparse.ArgumentParser() 192 | parser.add_argument("--order", default=3, type=int) 193 | parser.add_argument("--tfinal", default=0.015, type=float) 194 | parser.add_argument("--resolution", default=8, type=int) 195 | parser.add_argument("--oi", action="store_true", 196 | help="use overintegration") 197 | parser.add_argument("--lf", action="store_true", 198 | help="turn on lax-friedrichs dissipation") 199 | parser.add_argument("--visualize", action="store_true", 200 | help="write out vtk output") 201 | parser.add_argument("--lazy", action="store_true", 202 | help="switch to a lazy computation mode") 203 | args = parser.parse_args() 204 | 205 | logging.basicConfig(level=logging.INFO) 206 | main(cl.create_some_context, 207 | order=args.order, 208 | final_time=args.tfinal, 209 | resolution=args.resolution, 210 | overintegration=args.oi, 211 | lf_stabilization=args.lf, 212 | visualize=args.visualize, 213 | lazy=args.lazy) 214 | -------------------------------------------------------------------------------- /grudge/shortcuts.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | 4 | __copyright__ = "Copyright (C) 2009 Andreas Kloeckner" 5 | 6 | __license__ = """ 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | """ 25 | 26 | from functools import partial 27 | from typing import TYPE_CHECKING, Any, ClassVar, NamedTuple 28 | 29 | import pytools.obj_array as obj_array 30 | from arraycontext import BcastUntilActxArray 31 | from pytools import memoize_in 32 | 33 | from grudge.dof_desc import DD_VOLUME_ALL 34 | 35 | 36 | if TYPE_CHECKING: 37 | from collections.abc import Callable, Iterator 38 | 39 | from arraycontext.context import ArrayContext 40 | 41 | 42 | # {{{ legacy leap-like interface 43 | 44 | class StateComputedEvent(NamedTuple): 45 | t: float 46 | time_id: str 47 | component_id: str 48 | state_component: Any 49 | 50 | 51 | class LSRK4Method: 52 | StateComputed: ClassVar[type[StateComputedEvent]] = StateComputedEvent 53 | 54 | def __init__(self, 55 | f: Callable[[float, Any], Any], 56 | y0: Any, 57 | component_id: str, 58 | dt: float, 59 | tstart: float = 0.0) -> None: 60 | self.f = f 61 | self.y0 = y0 62 | self.dt = dt 63 | self.component_id = component_id 64 | self.tstart = tstart 65 | 66 | def run(self, 67 | t_end: float | None = None, 68 | max_steps: int | None = None) -> Iterator[StateComputedEvent]: 69 | t = self.tstart 70 | y = self.y0 71 | 72 | nsteps = 0 73 | while True: 74 | if t_end is not None and t >= t_end: 75 | return 76 | 77 | if max_steps is not None and nsteps >= max_steps: 78 | return 79 | 80 | y = lsrk4_step(y, t, self.dt, self.f) 81 | if t_end is not None: 82 | t += min(self.dt, t_end - t) 83 | else: 84 | t += self.dt 85 | 86 | yield StateComputedEvent(t=t, 87 | time_id="", 88 | component_id=self.component_id, 89 | state_component=y) 90 | 91 | nsteps += 1 92 | 93 | 94 | def stability_function(rk_a, rk_b): 95 | """Given the matrix *A* and the 'output coefficients' *b* from a Butcher 96 | tableau, return the stability function of the method as a 97 | :class:`sympy.core.expr.Expr`. 98 | """ 99 | import sympy as sp 100 | 101 | num_stages = len(rk_a) 102 | rk_a_mat = [ 103 | list(row) + [0]*(num_stages-len(row)) 104 | for row in rk_a] 105 | a = sp.Matrix(rk_a_mat) 106 | b = sp.Matrix(rk_b) 107 | eye = sp.eye(num_stages) 108 | ones = sp.ones(num_stages, 1) 109 | 110 | h_lambda = sp.Symbol("h_lambda") 111 | 112 | # https://en.wikipedia.org/w/index.php?title=Runge%E2%80%93Kutta_methods&oldid=1065948515#Stability 113 | return h_lambda, ( 114 | (eye - h_lambda * a + h_lambda * ones * b.T).det() 115 | / (eye - h_lambda * a).det()) 116 | 117 | 118 | RK4_A = ( 119 | (), 120 | (1/2,), 121 | (0, 1/2,), 122 | (0, 0, 1,), 123 | ) 124 | 125 | RK4_B = (1/6, 1/3, 1/3, 1/6) 126 | 127 | LSRK4_A = ( 128 | 0.0, 129 | -567301805773 / 1357537059087, 130 | -2404267990393 / 2016746695238, 131 | -3550918686646 / 2091501179385, 132 | -1275806237668 / 842570457699, 133 | ) 134 | 135 | LSRK4_B = ( 136 | 1432997174477 / 9575080441755, 137 | 5161836677717 / 13612068292357, 138 | 1720146321549 / 2090206949498, 139 | 3134564353537 / 4481467310338, 140 | 2277821191437 / 14882151754819, 141 | ) 142 | 143 | LSRK4_C = ( 144 | 0.0, 145 | 1432997174477/9575080441755, 146 | 2526269341429/6820363962896, 147 | 2006345519317/3224310063776, 148 | 2802321613138/2924317926251, 149 | # 1, 150 | ) 151 | 152 | # }}} 153 | 154 | 155 | def rk4_step(y, t, h, f): 156 | k1 = f(t, y) 157 | k2 = f(t+h/2, y + h/2*k1) 158 | k3 = f(t+h/2, y + h/2*k2) 159 | k4 = f(t+h, y + h*k3) 160 | return y + h/6*(k1 + 2*k2 + 2*k3 + k4) 161 | 162 | 163 | def lsrk4_step(y, t, h, f): 164 | p = k = y 165 | for a, b, c in zip(LSRK4_A, LSRK4_B, LSRK4_C, strict=True): 166 | k = a * k + h * f(t + c * h, p) 167 | p = p + b * k 168 | 169 | return p 170 | 171 | 172 | def _lsrk45_update(actx: ArrayContext, y, a, b, h, rhs_val, residual=None): 173 | bcast = partial(BcastUntilActxArray, actx) 174 | if residual is None: 175 | residual = bcast(h) * rhs_val 176 | else: 177 | residual = bcast(a) * residual + bcast(h) * rhs_val 178 | 179 | y = y + bcast(b) * residual 180 | return obj_array.new_1d([y, residual]) 181 | 182 | 183 | def compiled_lsrk45_step(actx: ArrayContext, y, t, h, f): 184 | @memoize_in(actx, (compiled_lsrk45_step, "update")) 185 | def get_state_updater(): 186 | return actx.compile(partial(_lsrk45_update, actx)) 187 | 188 | update = get_state_updater() 189 | 190 | residual = None 191 | 192 | for a, b, c in zip(LSRK4_A, LSRK4_B, LSRK4_C, strict=True): 193 | rhs_val = f(t + c*h, y) 194 | if residual is None: 195 | y, residual = update(y, a, b, h, rhs_val) 196 | else: 197 | y, residual = update(y, a, b, h, rhs_val, residual) 198 | 199 | return y 200 | 201 | 202 | def set_up_rk4(field_var_name, dt, fields, rhs, t_start=0.0): 203 | return LSRK4Method( 204 | rhs, 205 | fields, 206 | field_var_name, 207 | dt, 208 | tstart=t_start) 209 | 210 | 211 | def make_visualizer(dcoll, vis_order=None, volume_dd=None, **kwargs): 212 | from meshmode.discretization.visualization import make_visualizer 213 | if volume_dd is None: 214 | volume_dd = DD_VOLUME_ALL 215 | 216 | return make_visualizer( 217 | dcoll._setup_actx, 218 | dcoll.discr_from_dd(volume_dd), vis_order, **kwargs) 219 | 220 | 221 | def make_boundary_visualizer(dcoll, vis_order=None, **kwargs): 222 | from meshmode.discretization.visualization import make_visualizer 223 | from meshmode.mesh import BTAG_ALL 224 | 225 | return make_visualizer( 226 | dcoll._setup_actx, dcoll.discr_from_dd(BTAG_ALL), 227 | vis_order, **kwargs) 228 | -------------------------------------------------------------------------------- /test/test_tools.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | 4 | __copyright__ = """ 5 | Copyright (C) 2022 University of Illinois Board of Trustees 6 | """ 7 | 8 | __license__ = """ 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in 17 | all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | THE SOFTWARE. 26 | """ 27 | 28 | from dataclasses import dataclass 29 | 30 | import numpy as np 31 | 32 | from arraycontext import pytest_generate_tests_for_array_contexts 33 | 34 | from grudge.array_context import PytestPyOpenCLArrayContextFactory 35 | 36 | 37 | pytest_generate_tests = pytest_generate_tests_for_array_contexts( 38 | [PytestPyOpenCLArrayContextFactory]) 39 | 40 | import logging 41 | 42 | import pytest 43 | 44 | import pytools.obj_array as obj_array 45 | 46 | 47 | logger = logging.getLogger(__name__) 48 | 49 | 50 | # {{{ map_subarrays and rec_map_subarrays 51 | 52 | @dataclass(frozen=True, eq=True) 53 | class _DummyScalar: 54 | val: int 55 | 56 | 57 | def test_map_subarrays(): 58 | """Test map_subarrays.""" 59 | from grudge.tools import map_subarrays 60 | 61 | # Scalar 62 | result = map_subarrays( 63 | lambda x: np.array([x, 2*x]), (), (2,), 1) 64 | assert result.dtype == int 65 | assert np.all(result == np.array([1, 2])) 66 | 67 | # Scalar, nested 68 | result = map_subarrays( 69 | lambda x: np.array([x, 2*x]), (), (2,), 1, return_nested=True) 70 | assert result.dtype == int 71 | assert np.all(result == np.array([1, 2])) # Same as non-nested 72 | 73 | # in_shape is whole array 74 | result = map_subarrays( 75 | lambda x: 2*x, (2,), (2,), np.array([1, 2])) 76 | assert result.dtype == int 77 | assert np.all(result == np.array([2, 4])) 78 | 79 | # in_shape is whole array, nested 80 | result = map_subarrays( 81 | lambda x: 2*x, (2,), (2,), np.array([1, 2]), return_nested=True) 82 | assert result.dtype == int 83 | assert np.all(result == np.array([2, 4])) # Same as non-nested 84 | 85 | # len(out_shape) == 0 86 | result = map_subarrays( 87 | np.sum, (2,), (), np.array([[1, 2], [2, 4]])) 88 | assert result.dtype == int 89 | assert np.all(result == np.array([3, 6])) 90 | 91 | # len(out_shape) == 0, nested output 92 | result = map_subarrays( 93 | np.sum, (2,), (), np.array([[1, 2], [2, 4]]), return_nested=True) 94 | assert np.all(result == np.array([3, 6])) # Same as non-nested output 95 | 96 | # len(out_shape) == 0, non-numerical scalars 97 | result = map_subarrays( 98 | lambda x: _DummyScalar(x[0].val + x[1].val), (2,), (), 99 | np.array([ 100 | [_DummyScalar(1), _DummyScalar(2)], 101 | [_DummyScalar(2), _DummyScalar(4)]])) 102 | assert result.dtype == object 103 | assert result.shape == (2,) 104 | assert result[0] == _DummyScalar(3) 105 | assert result[1] == _DummyScalar(6) 106 | 107 | # len(out_shape) != 0 108 | result = map_subarrays( 109 | lambda x: np.array([x, 2*x]), (), (2,), np.array([1, 2])) 110 | assert result.dtype == int 111 | assert np.all(result == np.array([[1, 2], [2, 4]])) 112 | 113 | # len(out_shape) != 0, nested 114 | result = map_subarrays( 115 | lambda x: np.array([x, 2*x]), (), (2,), np.array([1, 2]), return_nested=True) 116 | assert result.dtype == object 117 | assert result.shape == (2,) 118 | assert np.all(result[0] == np.array([1, 2])) 119 | assert np.all(result[1] == np.array([2, 4])) 120 | 121 | # len(out_shape) != 0, non-numerical scalars 122 | result = map_subarrays( 123 | lambda x: np.array([_DummyScalar(x), _DummyScalar(2*x)]), (), (2,), 124 | np.array([1, 2])) 125 | assert result.dtype == object 126 | assert result.shape == (2, 2) 127 | assert np.all(result[0] == np.array([_DummyScalar(1), _DummyScalar(2)])) 128 | assert np.all(result[1] == np.array([_DummyScalar(2), _DummyScalar(4)])) 129 | 130 | # Zero-size input array 131 | result = map_subarrays( 132 | lambda x: np.array([x, 2*x]), (), (2,), np.empty((2, 0))) 133 | assert result.dtype == object 134 | assert result.shape == (2, 0, 2) 135 | 136 | # Zero-size input array, nested 137 | result = map_subarrays( 138 | lambda x: np.array([x, 2*x]), (), (2,), np.empty((2, 0)), 139 | return_nested=True) 140 | assert result.dtype == object 141 | assert result.shape == (2, 0) 142 | 143 | 144 | def test_rec_map_subarrays(): 145 | """Test rec_map_subarrays.""" 146 | from grudge.tools import rec_map_subarrays 147 | 148 | # Scalar 149 | result = rec_map_subarrays( 150 | lambda x: np.array([x, 2*x]), (), (2,), 1) 151 | assert result.dtype == int 152 | assert np.all(result == np.array([1, 2])) 153 | 154 | # Scalar, non-numerical 155 | result = rec_map_subarrays( 156 | lambda x: np.array([x.val, 2*x.val]), (), (2,), _DummyScalar(1), 157 | scalar_cls=_DummyScalar) 158 | assert result.dtype == int 159 | assert np.all(result == np.array([1, 2])) 160 | 161 | # Array of scalars 162 | result = rec_map_subarrays( 163 | np.sum, (2,), (), np.array([[1, 2], [2, 4]])) 164 | assert result.dtype == int 165 | assert np.all(result == np.array([3, 6])) 166 | 167 | # Array of scalars, non-numerical 168 | result = rec_map_subarrays( 169 | lambda x: x[0].val + x[1].val, (2,), (), 170 | np.array([ 171 | [_DummyScalar(1), _DummyScalar(2)], 172 | [_DummyScalar(2), _DummyScalar(4)]]), 173 | scalar_cls=_DummyScalar) 174 | assert result.dtype == int 175 | assert np.all(result == np.array([3, 6])) 176 | 177 | # Array container 178 | result = rec_map_subarrays( 179 | np.sum, (2,), (), obj_array.new_1d([np.array([1, 2]), np.array([2, 4])])) 180 | assert result.dtype == object 181 | assert result[0] == 3 182 | assert result[1] == 6 183 | 184 | # Array container, non-numerical scalars 185 | result = rec_map_subarrays( 186 | lambda x: x[0].val + x[1], (2,), (), 187 | obj_array.new_1d([ 188 | np.array([_DummyScalar(1), 2]), 189 | np.array([_DummyScalar(2), 4])]), 190 | scalar_cls=_DummyScalar) 191 | assert result.dtype == object 192 | assert result[0] == 3 193 | assert result[1] == 6 194 | 195 | # }}} 196 | 197 | 198 | # You can test individual routines by typing 199 | # $ python test_tools.py 'test_routine()' 200 | 201 | if __name__ == "__main__": 202 | import sys 203 | if len(sys.argv) > 1: 204 | exec(sys.argv[1]) 205 | else: 206 | pytest.main([__file__]) 207 | 208 | # vim: fdm=marker 209 | -------------------------------------------------------------------------------- /examples/advection/var-velocity.py: -------------------------------------------------------------------------------- 1 | __copyright__ = """ 2 | Copyright (C) 2017 Bogdan Enache 3 | Copyright (C) 2021 University of Illinois Board of Trustees 4 | """ 5 | 6 | __license__ = """ 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | """ 25 | 26 | import logging 27 | import os 28 | 29 | import numpy as np 30 | 31 | import pyopencl as cl 32 | import pyopencl.tools as cl_tools 33 | import pytools.obj_array as obj_array 34 | from arraycontext import flatten 35 | from meshmode.mesh import BTAG_ALL 36 | 37 | import grudge.dof_desc as dof_desc 38 | import grudge.op as op 39 | from grudge.array_context import PyOpenCLArrayContext 40 | 41 | 42 | logger = logging.getLogger(__name__) 43 | 44 | 45 | # {{{ plotting (keep in sync with `weak.py`) 46 | 47 | class Plotter: 48 | def __init__(self, actx, dcoll, order, visualize=True, ylim=None): 49 | self.actx = actx 50 | self.dim = dcoll.ambient_dim 51 | 52 | self.visualize = visualize 53 | if not self.visualize: 54 | return 55 | 56 | if self.dim == 1: 57 | import matplotlib.pyplot as pt 58 | self.fig = pt.figure(figsize=(8, 8), dpi=300) 59 | self.ylim = ylim 60 | 61 | volume_discr = dcoll.discr_from_dd(dof_desc.DD_VOLUME_ALL) 62 | self.x = actx.to_numpy(flatten(volume_discr.nodes()[0], self.actx)) 63 | else: 64 | from grudge.shortcuts import make_visualizer 65 | self.vis = make_visualizer(dcoll) 66 | 67 | def __call__(self, evt, basename, overwrite=True): 68 | if not self.visualize: 69 | return 70 | 71 | if self.dim == 1: 72 | u = self.actx.to_numpy(flatten(evt.state_component, self.actx)) 73 | 74 | filename = f"{basename}.png" 75 | if not overwrite and os.path.exists(filename): 76 | from meshmode import FileExistsError 77 | raise FileExistsError(f"output file '{filename}' already exists") 78 | 79 | ax = self.fig.gca() 80 | ax.plot(self.x, u, "-") 81 | ax.plot(self.x, u, "k.") 82 | if self.ylim is not None: 83 | ax.set_ylim(self.ylim) 84 | 85 | ax.set_xlabel("$x$") 86 | ax.set_ylabel("$u$") 87 | ax.set_title(f"t = {evt.t:.2f}") 88 | self.fig.savefig(filename) 89 | self.fig.clf() 90 | else: 91 | self.vis.write_vtk_file(f"{basename}.vtu", [ 92 | ("u", evt.state_component) 93 | ], overwrite=overwrite) 94 | 95 | # }}} 96 | 97 | 98 | def main(ctx_factory, dim=2, order=4, use_quad=False, visualize=False, 99 | flux_type="upwind"): 100 | cl_ctx = ctx_factory() 101 | queue = cl.CommandQueue(cl_ctx) 102 | 103 | allocator = cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)) 104 | actx = PyOpenCLArrayContext(queue, allocator=allocator) 105 | 106 | # {{{ parameters 107 | 108 | # domain [0, d]^dim 109 | d = 1.0 110 | # number of points in each dimension 111 | npoints = 25 112 | 113 | # final time 114 | final_time = 1 115 | 116 | if use_quad: 117 | qtag = dof_desc.DISCR_TAG_QUAD 118 | else: 119 | qtag = None 120 | 121 | # }}} 122 | 123 | # {{{ discretization 124 | 125 | from meshmode.mesh.generation import generate_regular_rect_mesh 126 | mesh = generate_regular_rect_mesh( 127 | a=(0,)*dim, b=(d,)*dim, 128 | npoints_per_axis=(npoints,)*dim, 129 | order=order) 130 | 131 | from meshmode.discretization.poly_element import QuadratureSimplexGroupFactory 132 | 133 | if use_quad: 134 | discr_tag_to_group_factory = { 135 | qtag: QuadratureSimplexGroupFactory(order=4*order) 136 | } 137 | else: 138 | discr_tag_to_group_factory = {} 139 | 140 | from grudge.discretization import make_discretization_collection 141 | 142 | dcoll = make_discretization_collection( 143 | actx, mesh, order=order, 144 | discr_tag_to_group_factory=discr_tag_to_group_factory 145 | ) 146 | 147 | # }}} 148 | 149 | # {{{ advection operator 150 | 151 | # gaussian parameters 152 | 153 | def f_halfcircle(x): 154 | source_center = np.array([d/2, d/2, d/2])[:dim] 155 | dist = x - source_center 156 | return ( 157 | (0.5+0.5*actx.np.tanh(500*(-np.dot(dist, dist) + 0.4**2))) 158 | * (0.5+0.5*actx.np.tanh(500*(dist[0])))) 159 | 160 | def zero_inflow_bc(dtag, t=0): 161 | dd = dof_desc.as_dofdesc(dtag, qtag) 162 | return dcoll.discr_from_dd(dd).zeros(actx) 163 | 164 | from grudge.models.advection import VariableCoefficientAdvectionOperator 165 | 166 | x = actx.thaw(dcoll.nodes()) 167 | 168 | # velocity field 169 | if dim == 1: 170 | c = x 171 | else: 172 | # solid body rotation 173 | c = obj_array.flat( 174 | np.pi * (d/2 - x[1]), 175 | np.pi * (x[0] - d/2), 176 | 0 177 | )[:dim] 178 | 179 | adv_operator = VariableCoefficientAdvectionOperator( 180 | dcoll, 181 | c, 182 | inflow_u=lambda t: zero_inflow_bc(BTAG_ALL, t), 183 | quad_tag=qtag, 184 | flux_type=flux_type 185 | ) 186 | 187 | u = f_halfcircle(x) 188 | 189 | def rhs(t, u): 190 | return adv_operator.operator(t, u) 191 | 192 | dt = actx.to_numpy(adv_operator.estimate_rk4_timestep(actx, dcoll, fields=u)) 193 | 194 | logger.info("Timestep size: %g", dt) 195 | 196 | # }}} 197 | 198 | # {{{ time stepping 199 | 200 | from grudge.shortcuts import set_up_rk4 201 | dt_stepper = set_up_rk4("u", float(dt), u, rhs) 202 | plot = Plotter(actx, dcoll, order, visualize=visualize, 203 | ylim=[-0.1, 1.1]) 204 | 205 | step = 0 206 | for event in dt_stepper.run(t_end=final_time): 207 | if not isinstance(event, dt_stepper.StateComputed): 208 | continue 209 | 210 | if step % 10 == 0: 211 | norm_u = actx.to_numpy(op.norm(dcoll, event.state_component, 2)) 212 | plot(event, f"fld-var-velocity-{step:04d}") 213 | 214 | logger.info("[%04d] t = %.5f |u| = %.5e", step, event.t, norm_u) 215 | # NOTE: These are here to ensure the solution is bounded for the 216 | # time interval specified 217 | assert norm_u < 1 218 | 219 | step += 1 220 | 221 | # }}} 222 | 223 | 224 | if __name__ == "__main__": 225 | import argparse 226 | 227 | parser = argparse.ArgumentParser() 228 | parser.add_argument("--dim", default=2, type=int) 229 | parser.add_argument("--order", default=4, type=int) 230 | parser.add_argument("--use-quad", action="store_true") 231 | parser.add_argument("--visualize", action="store_true") 232 | parser.add_argument("--flux", default="upwind", 233 | help="'central' or 'upwind'. Run with central to observe aliasing " 234 | "instability. Add --use-quad to fix that instability.") 235 | args = parser.parse_args() 236 | 237 | logging.basicConfig(level=logging.INFO) 238 | main(cl.create_some_context, 239 | dim=args.dim, 240 | order=args.order, 241 | use_quad=args.use_quad, 242 | visualize=args.visualize, 243 | flux_type=args.flux) 244 | -------------------------------------------------------------------------------- /test/test_dt_utils.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | 4 | __copyright__ = """ 5 | Copyright (C) 2021 University of Illinois Board of Trustees 6 | """ 7 | 8 | __license__ = """ 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in 17 | all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | THE SOFTWARE. 26 | """ 27 | 28 | import numpy as np 29 | 30 | import pytools.obj_array as obj_array 31 | from arraycontext import ArrayContextFactory, pytest_generate_tests_for_array_contexts 32 | 33 | from grudge.array_context import ( 34 | PytestNumpyArrayContextFactory, 35 | PytestPyOpenCLArrayContextFactory, 36 | PytestPytatoJAXArrayContextFactory, 37 | PytestPytatoPyOpenCLArrayContextFactory, 38 | ) 39 | 40 | 41 | pytest_generate_tests = pytest_generate_tests_for_array_contexts( 42 | [PytestPyOpenCLArrayContextFactory, 43 | PytestPytatoPyOpenCLArrayContextFactory, 44 | PytestNumpyArrayContextFactory, 45 | PytestPytatoJAXArrayContextFactory]) 46 | 47 | import logging 48 | 49 | import mesh_data 50 | import pytest 51 | 52 | import grudge.op as op 53 | from grudge.discretization import make_discretization_collection 54 | 55 | 56 | logger = logging.getLogger(__name__) 57 | from meshmode import _acf # noqa: F401 58 | 59 | 60 | @pytest.mark.parametrize("name", ["interval", "box2d", "box3d"]) 61 | def test_geometric_factors_regular_refinement(actx_factory: ArrayContextFactory, name): 62 | from grudge.dt_utils import dt_geometric_factors 63 | 64 | actx = actx_factory() 65 | 66 | # {{{ cases 67 | 68 | if name == "interval": 69 | builder = mesh_data.BoxMeshBuilder1D() 70 | elif name == "box2d": 71 | builder = mesh_data.BoxMeshBuilder2D() 72 | elif name == "box3d": 73 | builder = mesh_data.BoxMeshBuilder3D() 74 | else: 75 | raise ValueError(f"unknown geometry name: {name}") 76 | 77 | # }}} 78 | 79 | order = 4 80 | 81 | min_factors = [] 82 | for resolution in builder.resolutions: 83 | mesh = builder.get_mesh(resolution, order) 84 | dcoll = make_discretization_collection(actx, mesh, order=order) 85 | min_factors.append( 86 | actx.to_numpy( 87 | op.nodal_min(dcoll, "vol", actx.thaw(dt_geometric_factors(dcoll)))) 88 | ) 89 | 90 | # Resolution is doubled each refinement, so the ratio of consecutive 91 | # geometric factors should satisfy: gfi+1 / gfi = 2 92 | min_factors = np.asarray(min_factors) 93 | ratios = min_factors[:-1] / min_factors[1:] 94 | assert np.all(np.isclose(ratios, 2)) 95 | 96 | # Make sure it works with empty meshes 97 | mesh = builder.get_mesh(0) 98 | dcoll = make_discretization_collection(actx, mesh, order=order) 99 | factors = actx.thaw(dt_geometric_factors(dcoll)) # noqa: F841 100 | 101 | 102 | @pytest.mark.parametrize("name", ["interval", "box2d", "box3d"]) 103 | def test_non_geometric_factors(actx_factory: ArrayContextFactory, name): 104 | from grudge.dt_utils import dt_non_geometric_factors 105 | 106 | actx = actx_factory() 107 | 108 | # {{{ cases 109 | 110 | if name == "interval": 111 | builder = mesh_data.BoxMeshBuilder1D() 112 | elif name == "box2d": 113 | builder = mesh_data.BoxMeshBuilder2D() 114 | elif name == "box3d": 115 | builder = mesh_data.BoxMeshBuilder3D() 116 | else: 117 | raise ValueError(f"unknown geometry name: {name}") 118 | 119 | # }}} 120 | 121 | factors = [] 122 | degrees = list(range(1, 8)) 123 | for degree in degrees: 124 | mesh = builder.get_mesh(1, degree) 125 | dcoll = make_discretization_collection(actx, mesh, order=degree) 126 | factors.append(min(dt_non_geometric_factors(dcoll))) 127 | 128 | # Crude estimate, factors should behave like 1/N**2 129 | factors = np.asarray(factors) 130 | lower_bounds = 1/(np.asarray(degrees)**2) 131 | upper_bounds = 6.295*lower_bounds 132 | 133 | assert all(lower_bounds <= factors) 134 | assert all(factors <= upper_bounds) 135 | 136 | 137 | def test_build_jacobian(actx_factory: ArrayContextFactory): 138 | actx = actx_factory() 139 | import meshmode.mesh.generation as mgen 140 | 141 | mesh = mgen.generate_regular_rect_mesh(a=[0], b=[1], nelements_per_axis=(3,)) 142 | assert mesh.dim == 1 143 | 144 | dcoll = make_discretization_collection(actx, mesh, order=1) 145 | 146 | def rhs(x): 147 | return 3*x**2 + 2*x + 5 148 | 149 | base_state = obj_array.new_1d([dcoll.zeros(actx)+2]) 150 | 151 | from grudge.tools import build_jacobian 152 | mat = build_jacobian(actx, rhs, base_state, 1e-5) 153 | 154 | assert np.array_equal(mat, np.diag(np.diag(mat))) 155 | assert np.allclose(np.diag(mat), 3*2*2 + 2) 156 | 157 | 158 | @pytest.mark.parametrize("dim", [1, 2]) 159 | @pytest.mark.parametrize("degree", [2, 4]) 160 | def test_wave_dt_estimate( 161 | actx_factory: ArrayContextFactory, 162 | dim: int, 163 | degree: int, 164 | visualize: bool = False): 165 | actx = actx_factory() 166 | 167 | import meshmode.mesh.generation as mgen 168 | 169 | a = [0, 0, 0] 170 | b = [1, 1, 1] 171 | mesh = mgen.generate_regular_rect_mesh( 172 | a=a[:dim], b=b[:dim], 173 | nelements_per_axis=(3,)*dim) 174 | assert mesh.dim == dim 175 | 176 | dcoll = make_discretization_collection(actx, mesh, order=degree) 177 | 178 | from grudge.models.wave import WeakWaveOperator 179 | wave_op = WeakWaveOperator(dcoll, c=1) 180 | rhs = actx.compile( 181 | lambda w: wave_op.operator(t=0, w=w)) 182 | 183 | fields = obj_array.new_1d([dcoll.zeros(actx) for i in range(dim+1)]) 184 | 185 | from grudge.tools import build_jacobian 186 | mat = build_jacobian(actx, rhs, fields, 1) 187 | 188 | import numpy.linalg as la 189 | eigvals = la.eigvals(mat) 190 | 191 | assert (eigvals.real <= 1e-12).all() 192 | 193 | import sympy as sp 194 | 195 | from grudge.shortcuts import RK4_A, RK4_B, stability_function 196 | 197 | stab_func = sp.lambdify(*stability_function(RK4_A, RK4_B)) 198 | dt_est = actx.to_numpy(wave_op.estimate_rk4_timestep(actx, dcoll)) 199 | 200 | if visualize: 201 | re, im = np.mgrid[-4:1:30j, -5:5:30j] 202 | sf_grid = np.abs(stab_func(re+1j*im)) 203 | 204 | import matplotlib.pyplot as plt 205 | plt.contour(re, im, sf_grid, [0.25, 0.5, 0.75, 0.9, 1, 1.1]) 206 | plt.colorbar() 207 | plt.plot(dt_est * eigvals.real, dt_est * eigvals.imag, "x") 208 | plt.grid() 209 | plt.show() 210 | 211 | thresh = 1+1e-8 212 | max_stab = np.max(np.abs(stab_func(dt_est*eigvals))) 213 | assert max_stab < thresh, max_stab 214 | 215 | dt_factors = 2**np.linspace(0, 4, 40)[1:] 216 | stable_dt_factors = [ 217 | dt_factor 218 | for dt_factor in dt_factors 219 | if np.max(np.abs(stab_func(dt_factor*dt_est*eigvals))) < thresh] 220 | 221 | if stable_dt_factors: 222 | print(f"Stable timestep is {max(stable_dt_factors):.2f}x the estimate") 223 | else: 224 | print("Stable timestep estimate appears to be sharp") 225 | assert not stable_dt_factors or max(stable_dt_factors) < 1.5, stable_dt_factors 226 | 227 | 228 | # You can test individual routines by typing 229 | # $ python test_grudge.py 'test_routine()' 230 | 231 | if __name__ == "__main__": 232 | import sys 233 | if len(sys.argv) > 1: 234 | exec(sys.argv[1]) 235 | else: 236 | pytest.main([__file__]) 237 | -------------------------------------------------------------------------------- /examples/wave/wave-op-var-velocity.py: -------------------------------------------------------------------------------- 1 | __copyright__ = """ 2 | Copyright (C) 2020 Andreas Kloeckner 3 | Copyright (C) 2021 University of Illinois Board of Trustees 4 | """ 5 | 6 | __license__ = """ 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | """ 25 | 26 | 27 | import logging 28 | 29 | import numpy as np 30 | import numpy.linalg as la # noqa 31 | 32 | import pyopencl as cl 33 | import pyopencl.tools as cl_tools 34 | import pytools.obj_array as obj_array 35 | from meshmode.mesh import BTAG_ALL, BTAG_NONE # noqa 36 | 37 | import grudge.geometry as geo 38 | import grudge.op as op 39 | from grudge.array_context import PyOpenCLArrayContext 40 | from grudge.discretization import make_discretization_collection 41 | from grudge.dof_desc import DISCR_TAG_BASE, DISCR_TAG_QUAD, as_dofdesc 42 | from grudge.shortcuts import make_visualizer, rk4_step 43 | 44 | 45 | logger = logging.getLogger(__name__) 46 | 47 | 48 | # {{{ wave equation bits 49 | 50 | def wave_flux(actx, dcoll, c, w_tpair): 51 | dd = w_tpair.dd 52 | dd_quad = dd.with_discr_tag(DISCR_TAG_QUAD) 53 | 54 | u = w_tpair[0] 55 | v = w_tpair[1:] 56 | 57 | normal = geo.normal(actx, dcoll, dd) 58 | 59 | flux_weak = obj_array.flat( 60 | np.dot(v.avg, normal), 61 | normal*u.avg, 62 | ) 63 | 64 | # upwind 65 | flux_weak += obj_array.flat( 66 | 0.5*(u.ext-u.int), 67 | 0.5*normal*np.dot(normal, v.ext-v.int), 68 | ) 69 | 70 | # FIXME this flux is only correct for continuous c 71 | dd_allfaces_quad = dd_quad.with_domain_tag("all_faces") 72 | c_quad = op.project(dcoll, "vol", dd_quad, c) 73 | flux_quad = op.project(dcoll, dd, dd_quad, flux_weak) 74 | 75 | return op.project(dcoll, dd_quad, dd_allfaces_quad, c_quad*flux_quad) 76 | 77 | 78 | def wave_operator(actx, dcoll, c, w): 79 | u = w[0] 80 | v = w[1:] 81 | 82 | dir_u = op.project(dcoll, "vol", BTAG_ALL, u) 83 | dir_v = op.project(dcoll, "vol", BTAG_ALL, v) 84 | dir_bval = obj_array.flat(dir_u, dir_v) 85 | dir_bc = obj_array.flat(-dir_u, dir_v) 86 | 87 | dd_quad = as_dofdesc("vol", DISCR_TAG_QUAD) 88 | c_quad = op.project(dcoll, "vol", dd_quad, c) 89 | w_quad = op.project(dcoll, "vol", dd_quad, w) 90 | u_quad = w_quad[0] 91 | v_quad = w_quad[1:] 92 | 93 | dd_allfaces_quad = as_dofdesc("all_faces", DISCR_TAG_QUAD) 94 | 95 | return ( 96 | op.inverse_mass( 97 | dcoll, 98 | obj_array.flat( 99 | -op.weak_local_div(dcoll, dd_quad, c_quad*v_quad), 100 | -op.weak_local_grad(dcoll, dd_quad, c_quad*u_quad) 101 | # pylint: disable=invalid-unary-operand-type 102 | ) + op.face_mass( 103 | dcoll, 104 | dd_allfaces_quad, 105 | wave_flux( 106 | actx, 107 | dcoll, c=c, 108 | w_tpair=op.bdry_trace_pair(dcoll, 109 | BTAG_ALL, 110 | interior=dir_bval, 111 | exterior=dir_bc) 112 | ) + sum( 113 | wave_flux(actx, dcoll, c=c, w_tpair=tpair) 114 | for tpair in op.interior_trace_pairs(dcoll, w) 115 | ) 116 | ) 117 | ) 118 | ) 119 | 120 | # }}} 121 | 122 | 123 | def estimate_rk4_timestep(actx, dcoll, c): 124 | from grudge.dt_utils import characteristic_lengthscales 125 | 126 | local_dts = characteristic_lengthscales(actx, dcoll) / c 127 | 128 | return op.nodal_min(dcoll, "vol", local_dts) 129 | 130 | 131 | def bump(actx, dcoll, t=0, width=0.05, center=None): 132 | if center is None: 133 | center = np.array([0.2, 0.35, 0.1]) 134 | 135 | center = center[:dcoll.dim] 136 | source_omega = 3 137 | 138 | nodes = actx.thaw(dcoll.nodes()) 139 | center_dist = obj_array.flat([ 140 | nodes[i] - center[i] 141 | for i in range(dcoll.dim) 142 | ]) 143 | 144 | return ( 145 | np.cos(source_omega*t) 146 | * actx.np.exp( 147 | -np.dot(center_dist, center_dist) 148 | / width**2)) 149 | 150 | 151 | def main(ctx_factory, dim=2, order=3, visualize=False): 152 | cl_ctx = ctx_factory() 153 | queue = cl.CommandQueue(cl_ctx) 154 | 155 | allocator = cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)) 156 | actx = PyOpenCLArrayContext(queue, allocator=allocator) 157 | 158 | nel_1d = 16 159 | from meshmode.mesh.generation import generate_regular_rect_mesh 160 | mesh = generate_regular_rect_mesh( 161 | a=(-0.5,)*dim, 162 | b=(0.5,)*dim, 163 | nelements_per_axis=(nel_1d,)*dim) 164 | 165 | logger.info("%d elements", mesh.nelements) 166 | 167 | from meshmode.discretization.poly_element import ( 168 | QuadratureSimplexGroupFactory, 169 | default_simplex_group_factory, 170 | ) 171 | dcoll = make_discretization_collection( 172 | actx, mesh, 173 | discr_tag_to_group_factory={ 174 | DISCR_TAG_BASE: default_simplex_group_factory(base_dim=dim, order=order), 175 | DISCR_TAG_QUAD: QuadratureSimplexGroupFactory(3*order), 176 | } 177 | ) 178 | 179 | # bounded above by 1 180 | c = 0.2 + 0.8*bump(actx, dcoll, center=np.zeros(3), width=0.5) 181 | dt = actx.to_numpy(0.5 * estimate_rk4_timestep(actx, dcoll, c=1)) 182 | 183 | fields = obj_array.flat( 184 | bump(actx, dcoll, ), 185 | [dcoll.zeros(actx) for i in range(dcoll.dim)] 186 | ) 187 | 188 | vis = make_visualizer(dcoll) 189 | 190 | def rhs(t, w): 191 | return wave_operator(actx, dcoll, c=c, w=w) 192 | 193 | logger.info("dt = %g", dt) 194 | 195 | t = 0 196 | t_final = 3 197 | istep = 0 198 | while t < t_final: 199 | fields = rk4_step(fields, t, dt, rhs) 200 | 201 | l2norm = actx.to_numpy(op.norm(dcoll, fields[0], 2)) 202 | 203 | if istep % 10 == 0: 204 | linfnorm = actx.to_numpy(op.norm(dcoll, fields[0], np.inf)) 205 | nodalmax = actx.to_numpy(op.nodal_max(dcoll, "vol", fields[0])) 206 | nodalmin = actx.to_numpy(op.nodal_min(dcoll, "vol", fields[0])) 207 | logger.info("step: %d t: %.8e L2: %.8e Linf: %.8e " 208 | "sol max: %.8e sol min: %.8e", 209 | istep, t, l2norm, linfnorm, nodalmax, nodalmin) 210 | if visualize: 211 | vis.write_vtk_file( 212 | f"fld-wave-eager-var-velocity-{istep:04d}.vtu", 213 | [ 214 | ("c", c), 215 | ("u", fields[0]), 216 | ("v", fields[1:]), 217 | ] 218 | ) 219 | 220 | t += dt 221 | istep += 1 222 | 223 | # NOTE: These are here to ensure the solution is bounded for the 224 | # time interval specified 225 | assert l2norm < 1 226 | 227 | 228 | if __name__ == "__main__": 229 | import argparse 230 | 231 | parser = argparse.ArgumentParser() 232 | parser.add_argument("--dim", default=2, type=int) 233 | parser.add_argument("--order", default=3, type=int) 234 | parser.add_argument("--visualize", action="store_true") 235 | args = parser.parse_args() 236 | 237 | logging.basicConfig(level=logging.INFO) 238 | main(cl.create_some_context, 239 | dim=args.dim, 240 | order=args.order, 241 | visualize=args.visualize) 242 | 243 | # vim: foldmethod=marker 244 | -------------------------------------------------------------------------------- /examples/euler/acoustic_pulse.py: -------------------------------------------------------------------------------- 1 | __copyright__ = """ 2 | Copyright (C) 2021 University of Illinois Board of Trustees 3 | """ 4 | 5 | __license__ = """ 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | """ 24 | 25 | 26 | import logging 27 | 28 | import numpy as np 29 | 30 | import pyopencl as cl 31 | import pyopencl.tools as cl_tools 32 | import pytools.obj_array as obj_array 33 | from arraycontext import ArrayContext 34 | from meshmode.mesh import BTAG_ALL 35 | 36 | import grudge.op as op 37 | from grudge.array_context import PyOpenCLArrayContext, PytatoPyOpenCLArrayContext 38 | from grudge.models.euler import ConservedEulerField, EulerOperator, InviscidWallBC 39 | from grudge.shortcuts import compiled_lsrk45_step 40 | 41 | 42 | logger = logging.getLogger(__name__) 43 | 44 | 45 | def gaussian_profile( 46 | actx: ArrayContext, 47 | x_vec, t=0, rho0=1.0, rhoamp=1.0, p0=1.0, gamma=1.4, 48 | center=None, velocity=None): 49 | 50 | dim = len(x_vec) 51 | if center is None: 52 | center = np.zeros(shape=(dim,)) 53 | if velocity is None: 54 | velocity = np.zeros(shape=(dim,)) 55 | 56 | lump_loc = center + t * velocity 57 | 58 | # coordinates relative to lump center 59 | rel_center = obj_array.new_1d( 60 | [x_vec[i] - lump_loc[i] for i in range(dim)] 61 | ) 62 | r = actx.np.sqrt(np.dot(rel_center, rel_center)) 63 | expterm = rhoamp * actx.np.exp(1 - r ** 2) 64 | 65 | mass = expterm + rho0 66 | mom = velocity.astype(object) * mass 67 | energy = (p0 / (gamma - 1.0)) + np.dot(mom, mom) / (2.0 * mass) 68 | 69 | return ConservedEulerField(mass=mass, energy=energy, momentum=mom) 70 | 71 | 72 | def make_pulse(amplitude, r0, w, r): 73 | dim = len(r) 74 | r_0 = np.zeros(dim) 75 | r_0 = r_0 + r0 76 | rel_center = obj_array.new_1d( 77 | [r[i] - r_0[i] for i in range(dim)] 78 | ) 79 | actx = r[0].array_context 80 | rms2 = w * w 81 | r2 = np.dot(rel_center, rel_center) / rms2 82 | return amplitude * actx.np.exp(-.5 * r2) 83 | 84 | 85 | def acoustic_pulse_condition(actx: ArrayContext, x_vec, t=0): 86 | dim = len(x_vec) 87 | vel = np.zeros(shape=(dim,)) 88 | orig = np.zeros(shape=(dim,)) 89 | uniform_gaussian = gaussian_profile( 90 | actx, 91 | x_vec, t=t, center=orig, velocity=vel, rhoamp=0.0) 92 | 93 | amplitude = 1.0 94 | width = 0.1 95 | pulse = make_pulse(amplitude, orig, width, x_vec) 96 | 97 | return ConservedEulerField( 98 | mass=uniform_gaussian.mass, 99 | energy=uniform_gaussian.energy + pulse, 100 | momentum=uniform_gaussian.momentum 101 | ) 102 | 103 | 104 | def run_acoustic_pulse(actx, 105 | order=3, 106 | final_time=1, 107 | resolution=16, 108 | overintegration=False, 109 | visualize=False): 110 | 111 | # eos-related parameters 112 | gamma = 1.4 113 | 114 | # {{{ discretization 115 | 116 | from meshmode.mesh.generation import generate_regular_rect_mesh 117 | 118 | dim = 2 119 | box_ll = -0.5 120 | box_ur = 0.5 121 | mesh = generate_regular_rect_mesh( 122 | a=(box_ll,)*dim, 123 | b=(box_ur,)*dim, 124 | nelements_per_axis=(resolution,)*dim) 125 | 126 | from meshmode.discretization.poly_element import ( 127 | QuadratureSimplexGroupFactory, 128 | default_simplex_group_factory, 129 | ) 130 | 131 | from grudge.discretization import make_discretization_collection 132 | from grudge.dof_desc import DISCR_TAG_BASE, DISCR_TAG_QUAD 133 | 134 | exp_name = f"fld-acoustic-pulse-N{order}-K{resolution}" 135 | if overintegration: 136 | exp_name += "-overintegrated" 137 | quad_tag = DISCR_TAG_QUAD 138 | else: 139 | quad_tag = None 140 | 141 | dcoll = make_discretization_collection( 142 | actx, mesh, 143 | discr_tag_to_group_factory={ 144 | DISCR_TAG_BASE: default_simplex_group_factory( 145 | base_dim=mesh.dim, order=order), 146 | DISCR_TAG_QUAD: QuadratureSimplexGroupFactory(2*order) 147 | } 148 | ) 149 | 150 | # }}} 151 | 152 | # {{{ Euler operator 153 | 154 | euler_operator = EulerOperator( 155 | dcoll, 156 | bdry_conditions={BTAG_ALL: InviscidWallBC()}, 157 | flux_type="lf", 158 | gamma=gamma, 159 | quadrature_tag=quad_tag 160 | ) 161 | 162 | def rhs(t, q): 163 | return euler_operator.operator(actx, t, q) 164 | 165 | compiled_rhs = actx.compile(rhs) 166 | 167 | from grudge.dt_utils import h_min_from_volume 168 | 169 | cfl = 0.125 170 | cn = 0.5*(order + 1)**2 171 | dt = cfl * actx.to_numpy(h_min_from_volume(dcoll)) / cn 172 | 173 | fields = acoustic_pulse_condition(actx, actx.thaw(dcoll.nodes())) 174 | 175 | logger.info("Timestep size: %g", dt) 176 | 177 | # }}} 178 | 179 | from grudge.shortcuts import make_visualizer 180 | 181 | vis = make_visualizer(dcoll) 182 | 183 | # {{{ time stepping 184 | 185 | step = 0 186 | t = 0.0 187 | while t < final_time: 188 | if step % 10 == 0: 189 | norm_q = actx.to_numpy(op.norm(dcoll, fields, 2)) 190 | logger.info("[%04d] t = %.5f |q| = %.5e", step, t, norm_q) 191 | if visualize: 192 | vis.write_vtk_file( 193 | f"{exp_name}-{step:04d}.vtu", 194 | [ 195 | ("rho", fields.mass), 196 | ("energy", fields.energy), 197 | ("momentum", fields.momentum) 198 | ] 199 | ) 200 | assert norm_q < 5 201 | 202 | fields = actx.thaw(actx.freeze(fields)) 203 | fields = compiled_lsrk45_step(actx, fields, t, dt, compiled_rhs) 204 | t += dt 205 | step += 1 206 | 207 | # }}} 208 | 209 | 210 | def main(ctx_factory, order=3, final_time=1, resolution=16, 211 | overintegration=False, visualize=False, lazy=False): 212 | cl_ctx = ctx_factory() 213 | queue = cl.CommandQueue(cl_ctx) 214 | 215 | allocator = cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)) 216 | if lazy: 217 | actx = PytatoPyOpenCLArrayContext(queue, allocator=allocator) 218 | else: 219 | actx = PyOpenCLArrayContext(queue, allocator=allocator) 220 | 221 | run_acoustic_pulse( 222 | actx, 223 | order=order, 224 | resolution=resolution, 225 | overintegration=overintegration, 226 | final_time=final_time, 227 | visualize=visualize 228 | ) 229 | 230 | 231 | if __name__ == "__main__": 232 | import argparse 233 | 234 | parser = argparse.ArgumentParser() 235 | parser.add_argument("--order", default=3, type=int) 236 | parser.add_argument("--tfinal", default=0.1, type=float) 237 | parser.add_argument("--resolution", default=16, type=int) 238 | parser.add_argument("--oi", action="store_true", 239 | help="use overintegration") 240 | parser.add_argument("--visualize", action="store_true", 241 | help="write out vtk output") 242 | parser.add_argument("--lazy", action="store_true", 243 | help="switch to a lazy computation mode") 244 | args = parser.parse_args() 245 | 246 | logging.basicConfig(level=logging.INFO) 247 | main(cl.create_some_context, 248 | order=args.order, 249 | final_time=args.tfinal, 250 | resolution=args.resolution, 251 | overintegration=args.oi, 252 | visualize=args.visualize, 253 | lazy=args.lazy) 254 | --------------------------------------------------------------------------------