I am able to reproduce the error I am getting in the following minimal example: Consider
\begin{align*}
&\sigma \boldsymbol{u} - div(\nu \nabla \boldsymbol{u}) = \boldsymbol{f}(T) \;\; \mbox{in} \;\; \Omega := [0,1] \times [0,1], \\
&-div(D \nabla T) + (\boldsymbol{u} \cdot \nabla) T = 0 \;\; \mbox{in} \;\; \Omega, \\
&\frac{\partial T}{\partial \boldsymbol{n}} = 0 \;\; \mbox{on} \;\; \Gamma_{U}, \Gamma_{D}, \; \boldsymbol{u} = \boldsymbol{0} \;\; \mbox{on} \;\; \Gamma, \; T = T_0 \;\; \mbox{on} \;\; \Gamma_L, \; T = T_1 \;\; \mbox{on} \;\; \Gamma_R.
\end{align*}
Here, \boldsymbol{f}(T) := (G_r T)g is a linear function of T and G_r is a positive constant and g is a vector. I tried solving this problem using the built in Newton solver (with parameter choices specified in the code):
from dolfin import *
import matplotlib.pyplot as plt
# Parameters
Ra = Constant(100); Rk = Constant(1); Pr = Constant(10); Da = Constant(10E-7); Le = Constant(0.8);
Sr = Constant(0); Du = Constant(.1); N = Constant(0);
GrT = Ra/(Pr*Da); GrC = N*GrT
Sc = Le*Pr
g = Constant((1,0))
nu = Constant(1)
sigma = 1/Da # Reaction Domination
#D
DT = as_matrix([[Rk/Pr, Du],[Sr, 1/Sc]])
# T0 and T1
TD0 = Constant(-1); TD1 = Constant(1);
# Mollified at the ends
# TD0 = Expression('(x[1]>0 && x[1] < 1) ? -1/(exp(1/((x[1])*(1-x[1])))) : 0', degree = 4)
# TD1 = Expression('(x[1]>0 && x[1] < 1) ? 1/(exp(1/((x[1])*(1-x[1])))) : 0', degree = 4)
uD = Constant((0,0))
mesh = UnitSquareMesh(30,30)
# Finite element spaces
cell = triangle;
k = int(1)
P1v = VectorElement('CR', cell, k)
P1s = FiniteElement('CR', cell, k)
elem = MixedElement([P1v, P1s])
W = FunctionSpace(mesh, elem)
v, s = TestFunctions(W)
delta = Function(W)
u, T = split(delta)
# Boundary
class Left(SubDomain):
def inside(self, x, on_boundary):
return near(x[0], 0.0)
class Right(SubDomain):
def inside(self, x, on_boundary):
return near(x[0], 1.0)
class Bottom(SubDomain):
def inside(self, x, on_boundary):
return near(x[1], 0.0)
class Top(SubDomain):
def inside(self, x, on_boundary):
return near(x[1], 1.0)
# Initialize sub-domain instances
left = Left()
top = Top()
right = Right()
bottom = Bottom()
# Initialize mesh function for boundary domains
boundaries = MeshFunction("size_t", mesh, mesh.topology().dim() - 1)
boundaries.set_all(0)
left.mark(boundaries, 1)
top.mark(boundaries, 2)
right.mark(boundaries, 3)
bottom.mark(boundaries, 4)
bc_uT = DirichletBC(W.sub(0), uD, boundaries, 2)
bc_uB = DirichletBC(W.sub(0), uD, boundaries, 4)
bc_uL = DirichletBC(W.sub(0), uD, boundaries, 1)
bc_uR = DirichletBC(W.sub(0), uD, boundaries, 3)
bc_TL = DirichletBC(W.sub(1), TD1, boundaries, 1)
bc_TR = DirichletBC(W.sub(1), TD0, boundaries, 3)
bc = [bc_uT, bc_uB, bc_uL, bc_uR,
bc_TL, bc_TR]
he = FacetArea(mesh)
n = FacetNormal(mesh)
StabC = sqrt(sigma)*10
# Penalty to handle reaction domination
PenaltyState = (StabC)/avg(he) * nu * dot((jump(u)),jump(v)) * dS + (StabC)/he*dot((nu*u),v) * ds
Eq1s = (inner(nu*grad(u), grad(v))*dx) + sigma*dot(u,v)*dx + PenaltyState
FY = (GrT*T)*g
Eq8s = inner(v,FY)*dx
# unwinding
upwindT = (abs(dot(avg(u),n('+')))*(jump(s)*jump(T)))*dS
Eq1Ts = dot((DT*grad(T)), grad(s))*dx + dot(u,grad(T))*s*dx + upwindT
F = Eq1s + Eq1Ts - Eq8s
# Solve
solve(F == 0, delta, bc, solver_parameters={"newton_solver":{"relative_tolerance": 1e-6}})
uh, Th = delta.split()
plt.figure(1)
plt.title('u_h')
pu = plot(uh)
plt.colorbar(pu)
plt.figure(2)
plt.title('T_h')
pT = plot(Th)
plt.colorbar(pT)
plt.show()
Observe that due to the choice of parameters, the boundary conditions T_0 = -1 and T_1 = 1 create singularities at the left and right corners of the domain, which the built in Newton solver is able to handle. Following is the output:
(fenicsproject) jaitushar@Jais-MacBook-Pro ~ % cd /Users/jaitushar ; /usr/bin/env /opt/anaconda3/envs/fenicsproject/bin/python /Users/jaitushar/.vscode/extens
ions/ms-python.python-2023.10.1/pythonFiles/lib/python/debugpy/adapter/../../debugpy/launcher 64109 -- /Users/jaitushar/Documents/myFEniCS/myExamples/MinimalEx
_II_Newton.py
No Jacobian form specified for nonlinear variational problem.
Differentiating residual form F to obtain Jacobian J = F'.
Solving nonlinear variational problem.
Newton iteration 0: r (abs) = 2.329e+01 (tol = 1.000e-10) r (rel) = 1.000e+00 (tol = 1.000e-06)
Newton iteration 1: r (abs) = 2.135e-01 (tol = 1.000e-10) r (rel) = 9.167e-03 (tol = 1.000e-06)
Newton iteration 2: r (abs) = 4.348e+00 (tol = 1.000e-10) r (rel) = 1.867e-01 (tol = 1.000e-06)
Newton iteration 3: r (abs) = 1.019e+00 (tol = 1.000e-10) r (rel) = 4.374e-02 (tol = 1.000e-06)
Newton iteration 4: r (abs) = 1.775e-01 (tol = 1.000e-10) r (rel) = 7.623e-03 (tol = 1.000e-06)
Newton iteration 5: r (abs) = 8.842e-03 (tol = 1.000e-10) r (rel) = 3.797e-04 (tol = 1.000e-06)
Newton iteration 6: r (abs) = 2.188e-05 (tol = 1.000e-10) r (rel) = 9.393e-07 (tol = 1.000e-06)
Newton solver finished in 6 iterations and 6 linear solver iterations.
However, when I code the same problem using a custom newton solver by providing Jacobian and residual manually and using a linear solver (mumps), the solver is not able to resolve the singularity and fluctuates. Following is the code:
# Import Libraries
from dolfin import *
import matplotlib.pyplot as plt
Ra = Constant(100); Rk = Constant(1); Pr = Constant(10); Da = Constant(10E-7); Le = Constant(0.8);
Sr = Constant(0); Du = Constant(.1); N = Constant(0);
GrT = Ra/(Pr*Da); GrC = N*GrT
Sc = Le*Pr
g = Constant((1,0))
nu = Constant(1)
sigma = 1/Da
DT = as_matrix([[Rk/Pr, Du],[Sr, 1/Sc]])
TD0 = Constant(-1); TD1 = Constant(1);
# TD0 = Expression('(x[1]>0 && x[1] < 1) ? -1/(exp(1/((x[1])*(1-x[1])))) : 0', degree = 4)
# TD1 = Expression('(x[1]>0 && x[1] < 1) ? 1/(exp(1/((x[1])*(1-x[1])))) : 0', degree = 4)
uD = Constant((0,0))
mesh = UnitSquareMesh(30,30)
# Finite element spaces
cell = triangle;
k = int(1)
P1v = VectorElement('CR', cell, k)
P1s = FiniteElement('CR', cell, k)
elem = MixedElement([P1v, P1s])
W = FunctionSpace(mesh, elem)
v, s = TestFunctions(W)
u, T = TrialFunctions(W)
# T0D = Expression('(x[1]>0.2+eps && x[1] < 0.8-eps) ? -1/(exp(1/((x[1]-0.2)*(0.8-x[1])))) : 0', eps = DOLFIN_EPS, degree = 2)
# T1D = Expression('(x[1]>0.2+eps && x[1] < 0.8-eps) ? 1/(exp(1/((x[1]-0.2)*(0.8-x[1])))) : 0', eps = DOLFIN_EPS, degree = 2)
# Boundary
class Left(SubDomain):
def inside(self, x, on_boundary):
return near(x[0], 0.0)
class Right(SubDomain):
def inside(self, x, on_boundary):
return near(x[0], 1.0)
class Bottom(SubDomain):
def inside(self, x, on_boundary):
return near(x[1], 0.0)
class Top(SubDomain):
def inside(self, x, on_boundary):
return near(x[1], 1.0)
# Initialize sub-domain instances
left = Left()
top = Top()
right = Right()
bottom = Bottom()
# Initialize mesh function for boundary domains
boundaries = MeshFunction("size_t", mesh, mesh.topology().dim() - 1)
boundaries.set_all(0)
left.mark(boundaries, 1)
top.mark(boundaries, 2)
right.mark(boundaries, 3)
bottom.mark(boundaries, 4)
bc_uT = DirichletBC(W.sub(0), uD, boundaries, 2)
bc_uB = DirichletBC(W.sub(0), uD, boundaries, 4)
bc_uL = DirichletBC(W.sub(0), uD, boundaries, 1)
bc_uR = DirichletBC(W.sub(0), uD, boundaries, 3)
bc_TL = DirichletBC(W.sub(1), TD1, boundaries, 1)
bc_TR = DirichletBC(W.sub(1), TD0, boundaries, 3)
bc = [bc_uT, bc_uB, bc_uL, bc_uR,
bc_TL, bc_TR]
# Initial guesses satisfying the boundary conditions
u0 = Function(W)
T0 = Function(W)
bc_TL.apply(T0.sub(1).vector()); bc_TR.apply(T0.sub(1).vector())
for bcs in bc:
bcs.homogenize()
# Mesh info
he = FacetArea(mesh)
n = FacetNormal(mesh)
# Assemble
iter = 0; Er = 1; a_tol = 1E-8
while (Er > a_tol) and (iter < 20):
iter += 1
############### Assemble non-linearities ###############
b = u0.sub(0)
StabC = sqrt(sigma)*10
PenaltyState = (StabC)/avg(he) * nu * dot((jump(u)),jump(v)) * dS + (StabC)/he*dot((nu*u),v) * ds
Eq1s = (inner(nu*grad(u), grad(v))*dx) + sigma*dot(u,v)*dx + PenaltyState
FY = (GrT*T0.sub(1))*g
Eq8s = inner(v,FY)*dx
upwindT = (abs(dot(avg(b),n('+')))*(jump(s)*jump(T)))*dS
Eq1Ts = dot((DT*grad(T)), grad(s))*dx + dot(b,grad(T))*s*dx + upwindT
# System
J = Eq1s + Eq1Ts
res = - action(Eq1s, u0) + Eq8s - action(Eq1Ts, T0)
# Solve
delta = Function(W)
solve(J == res, delta, bc, solver_parameters={'linear_solver':'mumps'})
un, Tn= delta.split()
# Termination criterion
error = (delta)**2*dx
Er = sqrt(abs(assemble(error)))
# Update the solution
u0.vector()[:] += un.vector()[:]
T0.vector()[:] += Tn.vector()[:]
print('|delta| = %e, iteration %d \n' % (Er, iter))
## Visualize
plt.figure(1)
plt.title('u_h')
pu = plot(u0.sub(0))
plt.colorbar(pu)
plt.figure(2)
plt.title('T_h')
pT = plot(T0.sub(1))
plt.colorbar(pT)
plt.show()
Output:
(fenicsproject) jaitushar@Jais-MacBook-Pro ~ % cd /Users/jaitushar ; /usr/bin/env /opt/anaconda3/envs/fenicsproject/bin/python /Users/jaitushar/.vscode/extens
ions/ms-python.python-2023.10.1/pythonFiles/lib/python/debugpy/adapter/../../debugpy/launcher 64168 -- /Users/jaitushar/Documents/myFEniCS/myExamples/MinimalEx
_II.py
Solving linear variational problem.
|delta| = 5.676462e-01, iteration 1
Solving linear variational problem.
|delta| = 5.546556e+00, iteration 2
Solving linear variational problem.
|delta| = 4.399264e-01, iteration 3
Solving linear variational problem.
|delta| = 4.358093e+00, iteration 4
Solving linear variational problem.
|delta| = 1.333091e-01, iteration 5
Solving linear variational problem.
|delta| = 1.318313e+00, iteration 6
Solving linear variational problem.
|delta| = 4.795220e-02, iteration 7
Solving linear variational problem.
|delta| = 4.542295e-01, iteration 8
Solving linear variational problem.
|delta| = 1.668294e-02, iteration 9
Solving linear variational problem.
|delta| = 1.470277e-01, iteration 10
Solving linear variational problem.
|delta| = 5.031541e-03, iteration 11
Solving linear variational problem.
|delta| = 4.315923e-02, iteration 12
Solving linear variational problem.
|delta| = 1.448626e-03, iteration 13
Solving linear variational problem.
|delta| = 1.234712e-02, iteration 14
Solving linear variational problem.
|delta| = 4.584585e-04, iteration 15
Solving linear variational problem.
|delta| = 3.990136e-03, iteration 16
Solving linear variational problem.
|delta| = 2.415253e-04, iteration 17
Solving linear variational problem.
|delta| = 2.231978e-03, iteration 18
Solving linear variational problem.
|delta| = 2.039454e-04, iteration 19
Solving linear variational problem.
|delta| = 1.926179e-03, iteration 20
Questions:
-
I suspect ill conditioning is being introduced due to the boundary conditions and the problem. How is the Newton solver able to handle this but not the custom newton solve using the linear solver that I wrote?
-
Does the Newton solver introduce some kind of preconditioning in the system by default? If so how can I use it in my linear solve step?
-
Is there anyway to resolve this? Any hints or ideas in this direction will help.
Thanks!