From bfc77955894d0e05e34d3055324bea58fbf36065 Mon Sep 17 00:00:00 2001
From: Kaushik Kulkarni <kaushikcfd@gmail.com>
Date: Tue, 10 Aug 2021 12:30:00 -0500
Subject: [PATCH] adds hello-grudge

Co-authored-by: Alex Fikl <alexfikl@gmail.com>
---
 doc/conf.py              |   3 +
 doc/index.rst            |  21 +++++++
 examples/hello-grudge.py | 125 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 149 insertions(+)
 create mode 100644 examples/hello-grudge.py

diff --git a/doc/conf.py b/doc/conf.py
index 202ba44c..921eec5a 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -5,6 +5,9 @@ _conf_url = \
 with urlopen(_conf_url) as _inf:
     exec(compile(_inf.read(), _conf_url, "exec"), globals())
 
+extensions = globals()["extensions"] + [
+    "matplotlib.sphinxext.plot_directive"]
+
 copyright = "2015-21, grudge contributors"
 author = "grudge contributors"
 
diff --git a/doc/index.rst b/doc/index.rst
index 303a7589..87f9522a 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -1,6 +1,27 @@
 Welcome to grudge's Documentation!
 ==================================
 
+Here's an example to solve the PDE
+
+.. math::
+    \begin{cases}
+    u_t + 2\pi u_x = 0, \\
+    u(0, t) = -\sin(2\pi t), \\
+    u(x, 0) = \sin(x),
+    \end{cases}
+
+on the domain :math:`x \in [0, 2\pi]`. We closely follow Chapter 3 of
+[Hesthaven_2008]_.
+
+
+.. literalinclude:: ../examples/hello-grudge.py
+   :start-after: BEGINEXAMPLE
+   :end-before: ENDEXAMPLE
+
+Plotting numerical solution ``uh`` in results in
+
+.. plot:: ../examples/hello-grudge.py
+
 Contents:
 
 .. toctree::
diff --git a/examples/hello-grudge.py b/examples/hello-grudge.py
new file mode 100644
index 00000000..bf93fd08
--- /dev/null
+++ b/examples/hello-grudge.py
@@ -0,0 +1,125 @@
+# Solves the PDE:
+# \begin{cases}
+#   u_t + 2\pi u_x = 0, \\
+#   u(0, t) = -\sin(2\pi t), \\
+#   u(x, 0) = \sin(x),
+# \end{cases}
+# on the domain $x \in [0, 2\pi]$. We closely follow Chapter 3 of
+# "Nodal Discontinuous Galerkin Methods" by Hesthaven & Warburton.
+
+# BEGINEXAMPLE
+import numpy as np
+import pyopencl as cl
+from grudge.discretization import DiscretizationCollection
+import grudge.op as op
+from meshmode.mesh.generation import generate_box_mesh
+from meshmode.array_context import PyOpenCLArrayContext
+from arraycontext import thaw
+from grudge.dof_desc import DTAG_BOUNDARY, FACE_RESTR_INTERIOR
+
+
+ctx = cl.create_some_context()
+queue = cl.CommandQueue(ctx)
+actx = PyOpenCLArrayContext(queue)
+
+nel = 10
+coords = np.linspace(0, 2*np.pi, nel)
+mesh = generate_box_mesh((coords,),
+                         boundary_tag_to_face={"left": ["-x"],
+                                               "right": ["+x"]})
+dcoll = DiscretizationCollection(actx, mesh, order=1)
+
+
+def initial_condition(x):
+    # 'x' contains ndim arrays.
+    # 'x[0]' gets the first coordinate value of all the nodes
+    return actx.np.sin(x[0])
+
+
+def left_boundary_condition(x, t):
+    return actx.np.sin(x[0] - 2 * np.pi * t)
+
+
+def flux(dcoll, u_tpair):
+    dd = u_tpair.dd
+    velocity = np.array([2 * np.pi])
+    normal = thaw(dcoll.normal(dd), actx)
+
+    v_dot_n = np.dot(velocity, normal)
+    u_upwind = actx.np.where(v_dot_n > 0,
+                             u_tpair.int, u_tpair.ext)
+    return u_upwind * v_dot_n
+
+
+vol_discr = dcoll.discr_from_dd("vol")
+left_bndry = DTAG_BOUNDARY("left")
+right_bndry = DTAG_BOUNDARY("right")
+
+x_vol = thaw(dcoll.nodes(), actx)
+x_bndry = thaw(dcoll.discr_from_dd(left_bndry).nodes(), actx)
+
+uh = initial_condition(x_vol)
+
+dt = 0.001
+t = 0
+t_final = 0.5
+
+# timestepper loop
+while t < t_final:
+    # extract the left boundary trace pair
+    lbnd_tpair = op.bv_trace_pair(dcoll,
+                                  dd=left_bndry,
+                                  interior=uh,
+                                  exterior=left_boundary_condition(x_bndry, t))
+    # extract the right boundary trace pair
+    rbnd_tpair = op.bv_trace_pair(dcoll,
+                                  dd=right_bndry,
+                                  interior=uh,
+                                  exterior=op.project(dcoll, "vol",
+                                                      right_bndry, uh))
+    # extract the trace pairs on the interior faces
+    interior_tpair = op.interior_trace_pair(dcoll,
+                                            uh)
+    Su = op.weak_local_grad(dcoll, uh)
+
+    lift = op.face_mass(dcoll,
+                        # left boundary weak-flux terms
+                        op.project(dcoll,
+                                   left_bndry, "all_faces",
+                                   flux(dcoll, lbnd_tpair))
+                        # right boundary weak-flux terms
+                        + op.project(dcoll,
+                                     right_bndry, "all_faces",
+                                     flux(dcoll, rbnd_tpair))
+                        # interior weak-flux terms
+                        + op.project(dcoll,
+                                     FACE_RESTR_INTERIOR, "all_faces",
+                                     flux(dcoll, interior_tpair)))
+
+    duh_by_dt = op.inverse_mass(dcoll,
+                                np.dot([2 * np.pi], Su) - lift)
+
+    # forward euler time step
+    uh = uh + dt * duh_by_dt
+    t += dt
+# ENDEXAMPLE
+
+
+# Plot the solution:
+def u_exact(x, t):
+    return actx.np.sin(x[0] - 2 * np.pi * t)
+
+
+assert op.norm(dcoll,
+               uh - u_exact(x_vol, t_final),
+               p=2) <= 0.1
+import matplotlib.pyplot as plt
+from arraycontext import to_numpy
+plt.plot(to_numpy(actx.np.ravel(x_vol[0][0]), actx),
+         to_numpy(actx.np.ravel(uh[0]), actx), label="Numerical")
+plt.plot(to_numpy(actx.np.ravel(x_vol[0][0]), actx),
+         to_numpy(actx.np.ravel(u_exact(x_vol, t_final)[0]), actx), label="Exact")
+plt.xlabel("$x$")
+plt.ylabel("$u$")
+plt.legend()
+plt.show()
-- 
GitLab