General Linear Elliptic Equation#
The GeneralMG2d
class implements support for a general elliptic equation of the form:
with inhomogeneous boundary conditions.
It subclasses the CellCenterMG2d
class, and the basic interface is the same
We will solve the above with
\begin{align} \alpha &= 10 \\ \beta &= xy + 1 \\ \gamma &= \hat{x} + \hat{y} \end{align}
and
\begin{align} f = &-\frac{\pi}{2}(x + 1)\sin\left( \frac{\pi y}{2}\right) \cos\left(\frac{\pi x}{2}\right ) \\ {} &-\frac{\pi}{2}(y + 1)\sin\left( \frac{\pi x}{2}\right) \cos\left(\frac{\pi y}{2}\right ) \\ {} &+ \left( \frac{-\pi^2 (xy+1)}{2} + 10\right ) \cos \left ( \frac{\pi x}{2} \right ) \cos\left ( \frac{\pi y}{2} \right ) \end{align}
on \([0, 1] \times [0,1]\) with boundary conditions:
\begin{align} \phi(x=0) &= \cos(\pi y/2) \\ \phi(x=1) &= 0 \\ \phi(y=0) &= \cos(\pi x/2) \\ \phi(y=1) &= 0 \end{align}
This has the exact solution:
[1]:
import numpy as np
import matplotlib.pyplot as plt
Setting up the solver#
[2]:
import pyro.mesh.boundary as bnd
import pyro.mesh.patch as patch
import pyro.multigrid.general_MG as gMG
For reference, we’ll define a function providing the analytic solution
[3]:
def true(x,y):
return np.cos(np.pi*x/2.0)*np.cos(np.pi*y/2.0)
Now the coefficents–note that since \(\gamma\) is a vector, we have a different function for each component
[4]:
def alpha(x,y):
return 10.0*np.ones_like(x)
def beta(x,y):
return x*y + 1.0
def gamma_x(x,y):
return np.ones_like(x)
def gamma_y(x,y):
return np.ones_like(x)
and the righthand side function
[5]:
def f(x,y):
return -0.5*np.pi*(x + 1.0)*np.sin(np.pi*y/2.0)*np.cos(np.pi*x/2.0) - \
0.5*np.pi*(y + 1.0)*np.sin(np.pi*x/2.0)*np.cos(np.pi*y/2.0) + \
(-np.pi**2*(x*y+1.0)/2.0 + 10.0)*np.cos(np.pi*x/2.0)*np.cos(np.pi*y/2.0)
Our inhomogeneous boundary conditions require a function that can be evaluated on the boundary to give the value
[6]:
def xl_func(y):
return np.cos(np.pi*y/2.0)
def yl_func(x):
return np.cos(np.pi*x/2.0)
Now we can setup our grid object and the coefficients, which are stored as a CellCenter2d
object. Note, the coefficients do not need to have the same boundary conditions as \(\phi\) (and for real problems, they may not). The one that matters the most is \(\beta\), since that will need to be averaged to the edges of the domain, so the boundary conditions on the coefficients are important.
Here we use Neumann boundary conditions
[7]:
nx = 128
ny = 128
g = patch.Grid2d(nx, ny, ng=1)
d = patch.CellCenterData2d(g)
bc_c = bnd.BC(xlb="neumann", xrb="neumann",
ylb="neumann", yrb="neumann")
d.register_var("alpha", bc_c)
d.register_var("beta", bc_c)
d.register_var("gamma_x", bc_c)
d.register_var("gamma_y", bc_c)
d.create()
a = d.get_var("alpha")
a[:,:] = alpha(g.x2d, g.y2d)
b = d.get_var("beta")
b[:,:] = beta(g.x2d, g.y2d)
gx = d.get_var("gamma_x")
gx[:,:] = gamma_x(g.x2d, g.y2d)
gy = d.get_var("gamma_y")
gy[:,:] = gamma_y(g.x2d, g.y2d)
Now we can setup the multigrid object
[8]:
a = gMG.GeneralMG2d(nx, ny,
xl_BC_type="dirichlet", yl_BC_type="dirichlet",
xr_BC_type="dirichlet", yr_BC_type="dirichlet",
xl_BC=xl_func,
yl_BC=yl_func,
coeffs=d,
verbose=1, vis=0, true_function=true)
cc data: nx = 2, ny = 2, ng = 1
nvars = 7
variables:
v: min: 0.0000000000 max: 0.0000000000
BCs: -x: dirichlet +x: dirichlet -y: dirichlet +y: dirichlet
f: min: 0.0000000000 max: 0.0000000000
BCs: -x: dirichlet +x: dirichlet -y: dirichlet +y: dirichlet
r: min: 0.0000000000 max: 0.0000000000
BCs: -x: dirichlet +x: dirichlet -y: dirichlet +y: dirichlet
alpha: min: 0.0000000000 max: 0.0000000000
BCs: -x: neumann +x: neumann -y: neumann +y: neumann
beta: min: 0.0000000000 max: 0.0000000000
BCs: -x: neumann +x: neumann -y: neumann +y: neumann
gamma_x: min: 0.0000000000 max: 0.0000000000
BCs: -x: neumann +x: neumann -y: neumann +y: neumann
gamma_y: min: 0.0000000000 max: 0.0000000000
BCs: -x: neumann +x: neumann -y: neumann +y: neumann
cc data: nx = 4, ny = 4, ng = 1
nvars = 7
variables:
v: min: 0.0000000000 max: 0.0000000000
BCs: -x: dirichlet +x: dirichlet -y: dirichlet +y: dirichlet
f: min: 0.0000000000 max: 0.0000000000
BCs: -x: dirichlet +x: dirichlet -y: dirichlet +y: dirichlet
r: min: 0.0000000000 max: 0.0000000000
BCs: -x: dirichlet +x: dirichlet -y: dirichlet +y: dirichlet
alpha: min: 0.0000000000 max: 0.0000000000
BCs: -x: neumann +x: neumann -y: neumann +y: neumann
beta: min: 0.0000000000 max: 0.0000000000
BCs: -x: neumann +x: neumann -y: neumann +y: neumann
gamma_x: min: 0.0000000000 max: 0.0000000000
BCs: -x: neumann +x: neumann -y: neumann +y: neumann
gamma_y: min: 0.0000000000 max: 0.0000000000
BCs: -x: neumann +x: neumann -y: neumann +y: neumann
cc data: nx = 8, ny = 8, ng = 1
nvars = 7
variables:
v: min: 0.0000000000 max: 0.0000000000
BCs: -x: dirichlet +x: dirichlet -y: dirichlet +y: dirichlet
f: min: 0.0000000000 max: 0.0000000000
BCs: -x: dirichlet +x: dirichlet -y: dirichlet +y: dirichlet
r: min: 0.0000000000 max: 0.0000000000
BCs: -x: dirichlet +x: dirichlet -y: dirichlet +y: dirichlet
alpha: min: 0.0000000000 max: 0.0000000000
BCs: -x: neumann +x: neumann -y: neumann +y: neumann
beta: min: 0.0000000000 max: 0.0000000000
BCs: -x: neumann +x: neumann -y: neumann +y: neumann
gamma_x: min: 0.0000000000 max: 0.0000000000
BCs: -x: neumann +x: neumann -y: neumann +y: neumann
gamma_y: min: 0.0000000000 max: 0.0000000000
BCs: -x: neumann +x: neumann -y: neumann +y: neumann
cc data: nx = 16, ny = 16, ng = 1
nvars = 7
variables:
v: min: 0.0000000000 max: 0.0000000000
BCs: -x: dirichlet +x: dirichlet -y: dirichlet +y: dirichlet
f: min: 0.0000000000 max: 0.0000000000
BCs: -x: dirichlet +x: dirichlet -y: dirichlet +y: dirichlet
r: min: 0.0000000000 max: 0.0000000000
BCs: -x: dirichlet +x: dirichlet -y: dirichlet +y: dirichlet
alpha: min: 0.0000000000 max: 0.0000000000
BCs: -x: neumann +x: neumann -y: neumann +y: neumann
beta: min: 0.0000000000 max: 0.0000000000
BCs: -x: neumann +x: neumann -y: neumann +y: neumann
gamma_x: min: 0.0000000000 max: 0.0000000000
BCs: -x: neumann +x: neumann -y: neumann +y: neumann
gamma_y: min: 0.0000000000 max: 0.0000000000
BCs: -x: neumann +x: neumann -y: neumann +y: neumann
cc data: nx = 32, ny = 32, ng = 1
nvars = 7
variables:
v: min: 0.0000000000 max: 0.0000000000
BCs: -x: dirichlet +x: dirichlet -y: dirichlet +y: dirichlet
f: min: 0.0000000000 max: 0.0000000000
BCs: -x: dirichlet +x: dirichlet -y: dirichlet +y: dirichlet
r: min: 0.0000000000 max: 0.0000000000
BCs: -x: dirichlet +x: dirichlet -y: dirichlet +y: dirichlet
alpha: min: 0.0000000000 max: 0.0000000000
BCs: -x: neumann +x: neumann -y: neumann +y: neumann
beta: min: 0.0000000000 max: 0.0000000000
BCs: -x: neumann +x: neumann -y: neumann +y: neumann
gamma_x: min: 0.0000000000 max: 0.0000000000
BCs: -x: neumann +x: neumann -y: neumann +y: neumann
gamma_y: min: 0.0000000000 max: 0.0000000000
BCs: -x: neumann +x: neumann -y: neumann +y: neumann
cc data: nx = 64, ny = 64, ng = 1
nvars = 7
variables:
v: min: 0.0000000000 max: 0.0000000000
BCs: -x: dirichlet +x: dirichlet -y: dirichlet +y: dirichlet
f: min: 0.0000000000 max: 0.0000000000
BCs: -x: dirichlet +x: dirichlet -y: dirichlet +y: dirichlet
r: min: 0.0000000000 max: 0.0000000000
BCs: -x: dirichlet +x: dirichlet -y: dirichlet +y: dirichlet
alpha: min: 0.0000000000 max: 0.0000000000
BCs: -x: neumann +x: neumann -y: neumann +y: neumann
beta: min: 0.0000000000 max: 0.0000000000
BCs: -x: neumann +x: neumann -y: neumann +y: neumann
gamma_x: min: 0.0000000000 max: 0.0000000000
BCs: -x: neumann +x: neumann -y: neumann +y: neumann
gamma_y: min: 0.0000000000 max: 0.0000000000
BCs: -x: neumann +x: neumann -y: neumann +y: neumann
cc data: nx = 128, ny = 128, ng = 1
nvars = 7
variables:
v: min: 0.0000000000 max: 0.0000000000
BCs: -x: dirichlet +x: dirichlet -y: dirichlet +y: dirichlet
f: min: 0.0000000000 max: 0.0000000000
BCs: -x: dirichlet +x: dirichlet -y: dirichlet +y: dirichlet
r: min: 0.0000000000 max: 0.0000000000
BCs: -x: dirichlet +x: dirichlet -y: dirichlet +y: dirichlet
alpha: min: 0.0000000000 max: 0.0000000000
BCs: -x: neumann +x: neumann -y: neumann +y: neumann
beta: min: 0.0000000000 max: 0.0000000000
BCs: -x: neumann +x: neumann -y: neumann +y: neumann
gamma_x: min: 0.0000000000 max: 0.0000000000
BCs: -x: neumann +x: neumann -y: neumann +y: neumann
gamma_y: min: 0.0000000000 max: 0.0000000000
BCs: -x: neumann +x: neumann -y: neumann +y: neumann
just as before, we specify the righthand side and initialize the solution
[9]:
a.init_zeros()
a.init_RHS(f(a.x2d, a.y2d))
Source norm = 1.775181492337501
Solving the system#
[10]:
a.solve(rtol=1.e-10)
source norm = 1.775181492337501
<<< beginning V-cycle (cycle 1) >>>
level = 6, nx = 128, residual change: 1.77518 → 188.933
level = 5, nx = 64, residual change: 129.938 → 56.2871
level = 4, nx = 32, residual change: 38.8869 → 18.7228
level = 3, nx = 16, residual change: 12.9261 → 6.74186
level = 2, nx = 8, residual change: 4.64648 → 2.06513
level = 1, nx = 4, residual change: 1.37453 → 0.0224452
bottom solve
level = 1, nx = 4, residual change: 0.0312525 → 0.0312525
level = 2, nx = 8, residual change: 2.80598 → 2.80598
level = 3, nx = 16, residual change: 8.7724 → 8.7724
level = 4, nx = 32, residual change: 19.591 → 19.591
level = 5, nx = 64, residual change: 50.4641 → 50.4641
level = 6, nx = 128, residual change: 160.213 → 160.213
cycle 1: relative err = 0.9999999999999981, residual err = 2.323786088373021
<<< beginning V-cycle (cycle 2) >>>
level = 6, nx = 128, residual change: 4.12514 → 2.42473
level = 5, nx = 64, residual change: 1.69154 → 1.04862
level = 4, nx = 32, residual change: 0.728342 → 0.455482
level = 3, nx = 16, residual change: 0.316533 → 0.221286
level = 2, nx = 8, residual change: 0.153325 → 0.0747197
level = 1, nx = 4, residual change: 0.0497494 → 0.000813357
bottom solve
level = 1, nx = 4, residual change: 0.00113252 → 0.00113252
level = 2, nx = 8, residual change: 0.101526 → 0.101526
level = 3, nx = 16, residual change: 0.298147 → 0.298147
level = 4, nx = 32, residual change: 0.521885 → 0.521885
level = 5, nx = 64, residual change: 0.991063 → 0.991063
level = 6, nx = 128, residual change: 2.04419 → 2.04419
cycle 2: relative err = 0.036315310129800826, residual err = 0.03283823443993396
<<< beginning V-cycle (cycle 3) >>>
level = 6, nx = 128, residual change: 0.0582938 → 0.0417201
level = 5, nx = 64, residual change: 0.0292467 → 0.0233563
level = 4, nx = 32, residual change: 0.0163063 → 0.0129066
level = 3, nx = 16, residual change: 0.00901111 → 0.00731526
level = 2, nx = 8, residual change: 0.0050815 → 0.00256253
level = 1, nx = 4, residual change: 0.00170641 → 2.79124e-05
bottom solve
level = 1, nx = 4, residual change: 3.88653e-05 → 3.88653e-05
level = 2, nx = 8, residual change: 0.00348191 → 0.00348191
level = 3, nx = 16, residual change: 0.010065 → 0.010065
level = 4, nx = 32, residual change: 0.0160323 → 0.0160323
level = 5, nx = 64, residual change: 0.0243037 → 0.0243037
level = 6, nx = 128, residual change: 0.0377753 → 0.0377753
cycle 3: relative err = 0.0012532978372415558, residual err = 0.0006216334987521017
<<< beginning V-cycle (cycle 4) >>>
level = 6, nx = 128, residual change: 0.00110351 → 0.000889832
level = 5, nx = 64, residual change: 0.00062574 → 0.00060774
level = 4, nx = 32, residual change: 0.000426042 → 0.000397674
level = 3, nx = 16, residual change: 0.000278462 → 0.000242683
level = 2, nx = 8, residual change: 0.000168818 → 8.63435e-05
level = 1, nx = 4, residual change: 5.75013e-05 → 9.40799e-07
bottom solve
level = 1, nx = 4, residual change: 1.30997e-06 → 1.30997e-06
level = 2, nx = 8, residual change: 0.000117324 → 0.000117324
level = 3, nx = 16, residual change: 0.000338509 → 0.000338509
level = 4, nx = 32, residual change: 0.000524953 → 0.000524953
level = 5, nx = 64, residual change: 0.000708087 → 0.000708087
level = 6, nx = 128, residual change: 0.000918517 → 0.000918517
cycle 4: relative err = 4.257466296364851e-05, residual err = 1.4967652930826935e-05
<<< beginning V-cycle (cycle 5) >>>
level = 6, nx = 128, residual change: 2.65703e-05 → 2.30982e-05
level = 5, nx = 64, residual change: 1.62749e-05 → 1.79061e-05
level = 4, nx = 32, residual change: 1.25859e-05 → 1.28807e-05
level = 3, nx = 16, residual change: 9.03506e-06 → 8.103e-06
level = 2, nx = 8, residual change: 5.6415e-06 → 2.90121e-06
level = 1, nx = 4, residual change: 1.93217e-06 → 3.16168e-08
bottom solve
level = 1, nx = 4, residual change: 4.40233e-08 → 4.40233e-08
level = 2, nx = 8, residual change: 3.94227e-06 → 3.94227e-06
level = 3, nx = 16, residual change: 1.14059e-05 → 1.14059e-05
level = 4, nx = 32, residual change: 1.7696e-05 → 1.7696e-05
level = 5, nx = 64, residual change: 2.28172e-05 → 2.28172e-05
level = 6, nx = 128, residual change: 2.72045e-05 → 2.72045e-05
cycle 5: relative err = 1.437223355768636e-06, residual err = 4.2910353907176844e-07
<<< beginning V-cycle (cycle 6) >>>
level = 6, nx = 128, residual change: 7.61737e-07 → 6.88796e-07
level = 5, nx = 64, residual change: 4.8583e-07 → 5.69884e-07
level = 4, nx = 32, residual change: 4.01145e-07 → 4.28873e-07
level = 3, nx = 16, residual change: 3.01132e-07 → 2.72291e-07
level = 2, nx = 8, residual change: 1.89676e-07 → 9.77049e-08
level = 1, nx = 4, residual change: 6.50717e-08 → 1.06486e-09
bottom solve
level = 1, nx = 4, residual change: 1.48271e-09 → 1.48271e-09
level = 2, nx = 8, residual change: 1.32767e-07 → 1.32767e-07
level = 3, nx = 16, residual change: 3.85631e-07 → 3.85631e-07
level = 4, nx = 32, residual change: 6.03884e-07 → 6.03884e-07
level = 5, nx = 64, residual change: 7.68242e-07 → 7.68242e-07
level = 6, nx = 128, residual change: 8.86509e-07 → 8.86509e-07
cycle 6: relative err = 4.849259894834445e-08, residual err = 1.3530556515124825e-08
<<< beginning V-cycle (cycle 7) >>>
level = 6, nx = 128, residual change: 2.40192e-08 → 2.21253e-08
level = 5, nx = 64, residual change: 1.56138e-08 → 1.88696e-08
level = 4, nx = 32, residual change: 1.32927e-08 → 1.44857e-08
level = 3, nx = 16, residual change: 1.01772e-08 → 9.19808e-09
level = 2, nx = 8, residual change: 6.40947e-09 → 3.30184e-09
level = 1, nx = 4, residual change: 2.19906e-09 → 3.59875e-11
bottom solve
level = 1, nx = 4, residual change: 5.01092e-11 → 5.01092e-11
level = 2, nx = 8, residual change: 4.48679e-09 → 4.48679e-09
level = 3, nx = 16, residual change: 1.30812e-08 → 1.30812e-08
level = 4, nx = 32, residual change: 2.0705e-08 → 2.0705e-08
level = 5, nx = 64, residual change: 2.62808e-08 → 2.62808e-08
level = 6, nx = 128, residual change: 2.99444e-08 → 2.99444e-08
cycle 7: relative err = 1.6392149576904378e-09, residual err = 4.458207725000789e-10
<<< beginning V-cycle (cycle 8) >>>
level = 6, nx = 128, residual change: 7.91413e-10 → 7.35586e-10
level = 5, nx = 64, residual change: 5.1922e-10 → 6.36466e-10
level = 4, nx = 32, residual change: 4.4855e-10 → 4.92822e-10
level = 3, nx = 16, residual change: 3.4637e-10 → 3.11941e-10
level = 2, nx = 8, residual change: 2.17418e-10 → 1.11945e-10
level = 1, nx = 4, residual change: 7.45572e-11 → 1.22015e-12
bottom solve
level = 1, nx = 4, residual change: 1.69894e-12 → 1.69894e-12
level = 2, nx = 8, residual change: 1.52121e-10 → 1.52121e-10
level = 3, nx = 16, residual change: 4.44914e-10 → 4.44914e-10
level = 4, nx = 32, residual change: 7.10977e-10 → 7.10977e-10
level = 5, nx = 64, residual change: 9.034e-10 → 9.034e-10
level = 6, nx = 128, residual change: 1.0238e-09 → 1.0238e-09
cycle 8: relative err = 5.555097426033948e-11, residual err = 1.5072807373286882e-11
Checking the result#
We can compare to the true solution
[11]:
v = a.get_solution()
b = true(a.x2d, a.y2d)
e = v - b
The norm of the error is
[12]:
print(f"{e.norm():20.10g}")
1.671934405e-05