# 19.1. Solver Layers#

Current situation:

• We implement a time-stepper

• inside the time-stepper we run some Newton-like method to solve a non-linear equation.

• Newton’s method computes residual and tangent, and uses some direct solver to invert.

Layer approach:

• We create a Preconditoner object for the BilinearForm object

• We create a LinearSolver object from the BilinearForm.mat and the Preconditioner object

• We create a NonlinearSolver from the BilinearForm and the LinearSolver object (or LinearSolver-class+args)

• We create a TimeStepper from a BilinearForm object and NonLinearSolver class+args

## 19.1.1. Example:#

setting up the model:

from ngsolve import *
from ngsolve.webgui import Draw

from netgen.occ import *
shape = Rectangle(1,0.1).Face()
shape.edges.Max(X).name="right"
shape.edges.Min(X).name="left"
shape.edges.Max(Y).name="top"
shape.edges.Min(Y).name="bot"
# shape.vertices.Min(X+Y).maxh=0.01
# shape.vertices.Min(X-Y).maxh=0.01
shape.vertices.Min(X+Y).hpref=1
shape.vertices.Min(X-Y).hpref=1

mesh = Mesh(OCCGeometry(shape, dim=2).GenerateMesh(maxh=0.05))
mesh.RefineHP(2)

E, nu = 210, 0.2
mu  = E / 2 / (1+nu)
lam = E * nu / ((1+nu)*(1-2*nu))

def C(u):
return F.trans * F

def NeoHooke (C):
return 0.5*mu*(Trace(C-Id(2)) + 2*mu/lam*Det(C)**(-lam/2/mu)-1)

factor = Parameter(0.5)
force = CoefficientFunction( (0,factor) )

fes = H1(mesh, order=4, dirichlet="left", dim=mesh.dim)
u,v  = fes.TnT()

a = BilinearForm(fes, symmetric=True)
a += Variation(NeoHooke(C(u)).Compile()*dx)
a += Variation((-InnerProduct(force,u)).Compile()*dx)
gfu = GridFunction(fes)
gfu.vec[:] = 0


### 19.1.1.1. Usual approach#

Call Newton’s method, with a couple of arguments:

solvers.Newton(a=a, u=gfu, inverse="sparsecholesky", printing=False)
scene = Draw (C(gfu)[0,0]-1, mesh, deformation=gfu, min=-0.1, max=0.1)


### 19.1.1.2. Solver layers approach:#

pre = preconditioners.MultiGrid(a)
linsolve = solvers.CGSolver(a.mat, pre, maxiter=20)
nlsolve = nonlinearsolvers.NewtonSolver(a=a, u=gfu, solver=linsolve)

gfu.vec[:] = 0
nlsolve.Solve(printing=False);

scene = Draw (C(gfu)[0,0]-1, mesh, deformation=gfu, min=-0.1, max=0.1);


Variation keeps the expression for the energy. This is not yet supported for timestepping. We can use symbolic differentiation to convert it to variational forms:

a = BilinearForm(NeoHooke(C(u)).Diff(u, v)*dx - force*v*dx)
gfu = GridFunction(fes)
a.Assemble()  # we need that to construct a CGSolver object outside (TODO: solver_cls, solver_args)

pre = preconditioners.MultiGrid(a)
linsolve = solvers.CGSolver(a.mat, pre, maxiter=20)
nlsolve = nonlinearsolvers.NewtonSolver(a=a, u=gfu, solver=linsolve)

# nlsolve = nonlinearsolvers.NewtonSolver(a=a, u=gfu, lin_solver_cls=solvers.CGSolver, lin_solver_args={ "pre":pre })

gfu.vec[:] = 0
nlsolve.Solve(printing=False);

Draw (C(gfu)[0,0]-1, mesh, deformation=gfu, min=-0.1, max=0.1);


## 19.1.2. Timesteppers#

• User provides the time-dependent equation as a variational form

• Brand new: time-derivative u.dt

force = CF( (0,-1))
eq = NeoHooke(C(u)).Diff(u,v) * dx - force * v * dx + u.dt.dt * v * dx

ts = timestepping.Newmark(equation=eq, dt=1e-1)

gfu = GridFunction(fes)
scene = Draw(gfu, deformation=True, center=(0,-0.3), radius=0.6)

def callback(t, gfu):
# print("t = ", t, "displacement = ", gfu(mesh(1,0.05)))
scene.Redraw()
ts.Integrate(gfu, start_time=0, end_time=10, callback=callback);


### 19.1.2.1. New features to work on exptression trees#

mesh = Mesh(unit_square.GenerateMesh(maxh=0.1))
fes = H1(mesh, order=3, dirichlet=".*")
u,v = fes.TnT()

equ = u.dt*v*dx + 1e-3*u*v*dx + grad(u)*grad(v)*dx - v*dx
print (equ)

coef binary operation '*', real
coef trial-function diffop = Id order-dt=1, real
coef test-function diffop = Id, real
VOL
coef binary operation '*', real
coef scale 0.001, real
coef trial-function diffop = Id, real
coef test-function diffop = Id, real
VOL
coef innerproduct, fix size = 2, real
coef trial-function diffop = grad, real, dim=2
coef test-function diffop = grad, real, dim=2
VOL
-1*(coef test-function diffop = Id, real
) VOL


we can extract all proxies (trial- or test-functions) from the variational form:

allproxies = equ.GetProxies(trial=True)
print ("proxies:")
for proxy in allproxies: print(proxy, " python dtorder = ", proxy.dt_order)

proxies:
coef trial-function diffop = Id order-dt=1, real
python dtorder =  1
coef trial-function diffop = Id, real
python dtorder =  0
coef trial-function diffop = grad, real, dim=2
python dtorder =  0

proxiesdt0 = [ prox for prox in allproxies if prox.dt_order==0 ]
proxiesdt1 = [ prox for prox in allproxies if prox.dt_order==1 ]

print ("order 0 proxies:")
print (*proxiesdt0)

print ("order 1 proxies:")
print (*proxiesdt1)

order 0 proxies:
coef trial-function diffop = Id, real
coef trial-function diffop = grad, real, dim=2

order 1 proxies:
coef trial-function diffop = Id order-dt=1, real


The time-dependent equation is

$\int \partial_t u v + uv + \nabla u \nabla v - 1 v \; dx = 0$

An implicit Euler time-stepping method is to solve

$\int \frac{u - u_n}{\tau} v + uv + \nabla u \nabla v - 1 v \; dx = 0$

where the unknown $$u$$ is the value of the new time-step $$u_{n+1}$$.

So we have to replace the

$\partial_t u \rightarrow \frac{u - u_n}{\tau}$
tau = Parameter(0.1)
gfuold = GridFunction(fes)

repl = {}
for prox in allproxies:
if prox.dt_order==1:
repl[prox] = 1/tau*(prox.anti_dt - prox.anti_dt.ReplaceFunction(gfuold))

for key,val in repl.items():
print ("replace:", key)
print ("by\n", val)

replace: coef trial-function diffop = Id order-dt=1, real

by
coef binary operation '*', real
coef binary operation '/', real
coef 1, real
coef N5ngfem28ParameterCoefficientFunctionIdEE, real
coef binary operation '-', real
coef trial-function diffop = Id, real
coef N6ngcomp31GridFunctionCoefficientFunctionE, real

ImplEuler_equ = equ.Replace(repl)
print (ImplEuler_equ)

coef binary operation '*', real
coef binary operation '*', real
coef binary operation '/', real
coef 1, real
coef N5ngfem28ParameterCoefficientFunctionIdEE, real
coef binary operation '-', real
coef trial-function diffop = Id, real
coef N6ngcomp31GridFunctionCoefficientFunctionE, real
coef test-function diffop = Id, real
VOL
coef binary operation '*', real
coef scale 0.001, real
coef trial-function diffop = Id, real
coef test-function diffop = Id, real
VOL
coef innerproduct, fix size = 2, real
coef trial-function diffop = grad, real, dim=2
coef test-function diffop = grad, real, dim=2
VOL
-1*(coef test-function diffop = Id, real
) VOL

bfIE = BilinearForm(ImplEuler_equ)
bfIE.Assemble()
pre = preconditioners.MultiGrid(bfIE)
linsolve = krylovspace.CGSolver(bfIE.mat, pre, maxiter=20)

gfu = GridFunction(fes)
nlsolve = nonlinearsolvers.NewtonSolver(a=bfIE, u=gfu, solver=linsolve)

scene = Draw(gfu, deformation=True, scale=10)
gfuold.vec[:] = 0

from time import sleep
for i in range(20):
nlsolve.Solve()
gfuold.vec[:] = gfu.vec
scene.Redraw()
sleep(0.2)