This page was generated from unit-8.4-spacetime_fitted/spacetime_fitted.ipynb.
8.4 Space-time discretizations on fitted geometryΒΆ
In this tutorial, we consider the heat equation with Dirichlet boundary conditions and want to solve it with a fitted DG-in-time space-time finite element method. The problem is posed on the domain \(\Omega = [0,1]^2\) and time in \([0,1]\). In detail, the PDE reads:
We calculate the right-hand side \(f\) such that the solution is \(u = \sin(\pi t) \cdot \sin(\pi x)^2 \cdot \sin(\pi y)^2\)
[1]:
from netgen.geom2d import unit_square
from ngsolve import *
from xfem import *
import time
from math import pi
ngsglobals.msg_level = 1
importing ngsxfem-2.1.2303.dev0
We opt for a first-order method in space and time and choose an appropriate timestep.
[2]:
# Space finite element order
order = 1
# Time finite element order
k_t = 1
# Final simulation time
tend = 1.0
# Time step
delta_t = 1 / 32
In ngsxfem
all space-time objects are designed for the reference time variable tref
living on the reference time interval \([0,1]\). To obtain integrals and time derivatives with respect to the current time interval corresponding operators or objects need to be rescaled:
[3]:
def dt(u):
return 1.0 / delta_t * dtref(u)
tnew = 0
told = Parameter(0)
t = told + delta_t * tref
t.MakeVariable()
[3]:
<ngsolve.fem.CoefficientFunction at 0x7faa690a66b0>
Next, we opt for a standard space-time finite element discretisation based on the previous parameters. The space-time finite element space is of tensor-product form where the finite element for the time direction, a ScalarTimeFE
uses a nodal basis.
[4]:
mesh = Mesh(unit_square.GenerateMesh(maxh=0.05, quad_dominated=False))
V = H1(mesh, order=order, dirichlet=".*")
tfe = ScalarTimeFE(k_t)
st_fes = tfe * V # tensor product of a space finite element space and a 1D finite element
For evaluation and time stepping the extraction of spatial functions from a space-time function will be required at several places. To obtain a spatial gridfunction from the right space (here V
) we can use CreateTimeRestrictedGF
which creates a new GridFunction
and sets the values according to the space-time GridFunction
restricted to a specific reference time.
[5]:
gfu = GridFunction(st_fes)
u_last = CreateTimeRestrictedGF(gfu, 1)
u, v = st_fes.TnT()
The exact solution is as stated above. With the help of a series of GridFunction
we can visualize the function as a function in space for different values of tref
that can be sampled by the multidim
-component and animated:
[6]:
u_exact = sin(pi * t) * sin(pi * x)**2 * sin(pi * y)**2
from helper import ProjectOnMultiDimGF
u_exact_to_show = ProjectOnMultiDimGF(sin(pi * tref) * sin(pi * x)**2 * sin(pi * y)**2,mesh,order=3,sampling=8)
Draw(u_exact_to_show,mesh,"u_exact",autoscale=False,min=0,max=1, interpolate_multidim=True, animate=True, deformation=True)
# The multidim-option now samples within the time interval [0,1].
/usr/lib/python3/dist-packages/scipy/__init__.py:146: UserWarning: A NumPy version >=1.17.3 and <1.25.0 is required for this version of SciPy (detected version 1.26.4
warnings.warn(f"A NumPy version >={np_minversion} and <{np_maxversion}"
[6]:
BaseWebGuiScene
The right-hand side \(f\) is calculated accordingly:
[7]:
coeff_f = u_exact.Diff(t) - (u_exact.Diff(x).Diff(x) + u_exact.Diff(y).Diff(y))
coeff_f = coeff_f.Compile()
The variational formulation is derived from partial integration (in space) of the problem given above. We introduce the DifferentialSymbol
s
[8]:
dxt = delta_t * dxtref(mesh, time_order=2)
dxold = dmesh(mesh, tref=0)
dxnew = dmesh(mesh, tref=1)
Here, dxt
corresponds to \(\int_Q \cdot d(x,t) = \int_{t^{n-1}}^{t^n} \int_{\Omega} \cdot d(x,t)\) whereas dxold
and dxnew
are spatial integrals on \(\Omega\) with however fixed reference time values 0 or 1 for the evaluation of space-time functions. With those, we can write the variational formulation as: Find \(u \in W_h = V_h \times \mathcal{P}^1([t^{n-1},t^n])\) so that
where \(u_+(\cdot,t^{n-1})\) is the value from the last time step (initial value).
[9]:
a = BilinearForm(st_fes, symmetric=False)
a += (dt(u) * v + grad(u) * grad(v)) * dxt
a += u * v * dxold
a.Assemble()
ainv = a.mat.Inverse(st_fes.FreeDofs())
f = LinearForm(st_fes)
f += coeff_f * v * dxt
f += u_last * v * dxold
Note that the l.h.s. does not depend on the time step. We hence assemble and factorize it only once. We use u_last
as the GridFunction
to hold the initial values. To initialize it, we take the space-time function u_exact
and restrict it to tref=0
using fix_tref
. Not that u_exact
depends on space and time whereas u_last
is merely a spatial function.
[10]:
u_last.Set(fix_tref(u_exact, 0))
told.Set(0.)
For visualization purposes we store the initial value (and later add the solutions after each time step):
[11]:
gfut = GridFunction(u_last.space, multidim=0)
gfut.AddMultiDimComponent(u_last.vec)
Finally, the problem is solved in a time-stepping manner, which includes: * Assemble of r.h.s. * solve time step solution * extract solution on new time level (RestrictGFInTime
extracts a spatial GridFunction
from a space-time one for a fixed value `tref) * storing time step solution for later visualization (see last cell) * measuring the error
[12]:
scene = Draw(u_last, mesh,"u", autoscale=False,min=0,max=1,deformation=True)
timestep = 0
while tend - told.Get() > delta_t / 2:
if timestep % 4 == 0:
gfut.AddMultiDimComponent(u_last.vec)
timestep += 1
f.Assemble()
gfu.vec.data = ainv * f.vec
RestrictGFInTime(spacetime_gf=gfu, reference_time=1.0, space_gf=u_last)
l2error = sqrt(Integrate((u_exact - gfu)**2 * dxnew, mesh))
scene.Redraw()
told.Set(told.Get() + delta_t)
print("\rt = {0:12.9f}, L2 error = {1:12.9e}".format(told.Get(), l2error), end="")
gfut.AddMultiDimComponent(u_last.vec)
t = 1.000000000, L2 error = 1.840270280e-04
Next, we can visualize the time evoluation again. Note also the controls under Multidim
(animate, speed, t).
[13]:
Draw (gfut, mesh, interpolate_multidim=True, animate=True, deformation=True, min=0, max=1, autoscale=False)
[13]:
BaseWebGuiScene