Hi, as I understand the “Real” element hasn’t yet been implemented in Dolfinx. So to solve Navier-Stokes equations coupled with rigid body motion equations in a Newton solver I have gone to assemble the NS equations using dolfinx and adding the four extra degrees of freedom myself onto the PETSc matrix using CSR matrix to feed into PETSc4Py’s SNES via a class interfacing with SNES. I can’t seem to get the problem to converge, so I played around with solving it with just the NS equations and it converges with a dolfinx create_matrix_block
but not a PETSc.Mat().createAIJ()
that is based on the size of the create_matrix_block
in snes.setJacobian()
. In fact, there seems to be different behaviour when I mix and match empty PETSc created matrices/vectors with dolfinx created matrices/vectors. As such I don’t think it’s a problem with my equations/mesh but rather with how I implement dolfinx/petsc. Below I have an MWE for a non-linear Poisson equation (from the tutorials) that Seg Faults for some unknown reason (see first example), and another MWE where I actually get and reassemble a CSR that just doesn’t converge.
Here is the MWE that Seg Faults by using a PETSc.Mat().createAIJ()
import ufl
import numpy
from mpi4py import MPI
from petsc4py import PETSc
from dolfinx import mesh, fem, io, nls, log
def q(u):
return 1 + u**2
domain = mesh.create_unit_square(MPI.COMM_WORLD, 10, 10)
x = ufl.SpatialCoordinate(domain)
u_ufl = 1 + x[0] + 2*x[1]
f = - ufl.div(q(u_ufl)*ufl.grad(u_ufl))
V = fem.FunctionSpace(domain, ("CG", 1))
u_exact = lambda x: eval(str(u_ufl))
u_D = fem.Function(V)
u_D.interpolate(u_exact)
fdim = domain.topology.dim - 1
boundary_facets = mesh.locate_entities_boundary(domain, fdim, lambda x: numpy.full(x.shape[1], True, dtype=bool))
bc = fem.dirichletbc(u_D, fem.locate_dofs_topological(V, fdim, boundary_facets))
uh = fem.Function(V)
v = ufl.TestFunction(V)
F = q(uh)*ufl.dot(ufl.grad(uh), ufl.grad(v))*ufl.dx - f*v*ufl.dx
class NonlinearPDE_SNESProblem:
def __init__(self, F, u, bc):
V = u.function_space
du = ufl.TrialFunction(V)
self.L = fem.form(F)
self.a = fem.form(ufl.derivative(F, u, du))
self.bc = bc
self._F, self._J = None, None
self.u = u
def F(self, snes, x, F):
"""Assemble residual vector."""
x.ghostUpdate(addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD)
x.copy(self.u.vector)
self.u.vector.ghostUpdate(addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD)
with F.localForm() as f_local:
f_local.set(0.0)
fem.petsc.assemble_vector(F, self.L)
fem.petsc.apply_lifting(F, [self.a], bcs=[[self.bc]], x0=[x], scale=-1.0)
F.ghostUpdate(addv=PETSc.InsertMode.ADD, mode=PETSc.ScatterMode.REVERSE)
fem.petsc.set_bc(F, [self.bc], x, -1.0)
def J(self, snes, x, J, P):
"""Assemble Jacobian matrix."""
J.zeroEntries()
fem.petsc.assemble_matrix(J, self.a, bcs=[self.bc])
J.assemble()
problem = NonlinearPDE_SNESProblem(F, uh, bc)
J_mat_dolfinx = fem.petsc.create_matrix(problem.a)
F_vec_dolfinx = fem.petsc.create_vector(problem.L)
J_mat = PETSc.Mat().createAIJ(J_mat_dolfinx.getSizes())
J_mat.setPreallocationNNZ(30)
J_mat.setUp()
F_vec = J_mat.createVecRight()
snes = PETSc.SNES().create(MPI.COMM_WORLD)
snes.getKSP().setType("preonly")
snes.getKSP().getPC().setType("lu")
snes.getKSP().getPC().setFactorSolverType("mumps")
snes.setFunction(problem.F, F_vec)
# This works and converges in 5 iterations
#snes.setJacobian(problem.J, J=J_mat_dolfinx, P=None)
# This doesn't
snes.setJacobian(problem.J, J=J_mat, P=None)
snes.setMonitor(lambda _, it, residual: print(it, residual))
solution = fem.petsc.create_vector(problem.L)
snes.solve(None, solution)
uh.x.array[:] = solution.getArray()[:]
Here is the MWE that doesn’t converge:
import sys
import ufl
import numpy
from mpi4py import MPI
from petsc4py import PETSc
from dolfinx import mesh, fem, io, nls, log
def q(u):
return 1 + u**2
domain = mesh.create_unit_square(MPI.COMM_WORLD, 10, 10)
x = ufl.SpatialCoordinate(domain)
u_ufl = 1 + x[0] + 2*x[1]
V = fem.FunctionSpace(domain, ("CG", 1))
u_exact = lambda x: eval(str(u_ufl))
u_D = fem.Function(V)
u_D.interpolate(u_exact)
fdim = domain.topology.dim - 1
boundary_facets = mesh.locate_entities_boundary(domain, fdim, lambda x: numpy.full(x.shape[1], True, dtype=bool))
bc = fem.dirichletbc(u_D, fem.locate_dofs_topological(V, fdim, boundary_facets))
uh = fem.Function(V)
v = ufl.TestFunction(V)
F = q(uh)*ufl.dot(ufl.grad(uh), ufl.grad(v))*ufl.dx
class NonlinearPDE_SNESProblem:
def __init__(self, F, u, bc):
V = u.function_space
du = ufl.TrialFunction(V)
self.L = fem.form(F)
self.a = fem.form(ufl.derivative(F, u, du))
self.bc = bc
self._F, self._J = None, None
self.u = u
self.J_mat_dolfinx = fem.petsc.create_matrix(self.a)
def F(self, snes, x, F):
"""Assemble residual vector."""
x.ghostUpdate(addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD)
x.copy(self.u.vector)
self.u.vector.ghostUpdate(addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD)
with F.localForm() as f_local:
f_local.set(0.0)
fem.petsc.assemble_vector(F, self.L)
fem.petsc.apply_lifting(F, [self.a], bcs=[[self.bc]], x0=[x], scale=-1.0)
F.ghostUpdate(addv=PETSc.InsertMode.ADD, mode=PETSc.ScatterMode.REVERSE)
fem.petsc.set_bc(F, [self.bc], x, -1.0)
def J(self, snes, x, J, P):
"""Assemble Jacobian matrix."""
J.zeroEntries()
fem.petsc.assemble_matrix(self.J_mat_dolfinx, self.a, bcs=[self.bc])
self.J_mat_dolfinx.assemble()
ai, aj, av = self.J_mat_dolfinx.getValuesCSR()
J.setPreallocationNNZ(numpy.diff(ai))
J.setValuesCSR(ai,aj,av)
J.assemble()
problem = NonlinearPDE_SNESProblem(F, uh, bc)
J_mat_dolfinx = fem.petsc.create_matrix(problem.a)
F_vec_dolfinx = fem.petsc.create_vector(problem.L)
J_mat = PETSc.Mat().createAIJ(J_mat_dolfinx.getSizes())
J_mat.setPreallocationNNZ(30)
J_mat.setUp()
F_vec = J_mat.createVecRight()
snes = PETSc.SNES().create(MPI.COMM_WORLD)
snes.setTolerances(max_it=1000, atol=1.e-14, rtol=1.e-14)
snes.getKSP().setType("preonly")
snes.getKSP().getPC().setType("lu")
snes.getKSP().getPC().setFactorSolverType("mumps")
snes.setFunction(problem.F, F_vec)
# Now this converges realatively but not absolutely and in 670 iterations)
#snes.setJacobian(problem.J, J=J_mat_dolfinx, P=None)
# And this has the exact same behaviour as using uncommenting the above line (i.e. no seg fault)
snes.setJacobian(problem.J, J=J_mat, P=None)
snes.setMonitor(lambda _, it, residual: print(it, residual))
solution = fem.petsc.create_vector(problem.L)
snes.solve(None, solution)
uh.x.array[:] = solution.getArray()[:]
Also saw this thread which is similar to my first problem, but the solution is untenable as I need to change the size of the PETSc vector/matrix to add degrees of freedom.