I am trying to reproduce an example from Mathematica:
u_1'(x)-u_2(x)=0
u_2'(x)+u_1(x)-5(1-u_1^2(x))u_2(x)=0
with u_1(0)=2 and u_2(0)=0.
The solution should be like this (confirmed from Mathamtica):
.
This is a nonlinear problem. So when solving the algebraic system, proper initial guess should be assigned. The function is shaking violently, so I choose a random array as the initial guess. To obtain this solution, I had to run the code again and again until I saw a convergence in the iteration. The reason might still lie on the shape of the functions. Also, if I shrank the interval, say [0, 4], it became easier to converge and harder when prolong the interval say [0, 15]. This is understandable from the same view: the shorter interval corresponds to smoother functions while the longer interval may introduce another peak. In the example I choose [0, 8]. I was wondering if there is a better way to solve the algebraic system. I was trying to adjust
snes.getKSP().setType("preonly")
snes.getKSP().getPC().setType("lu")
snes.getKSP().getPC().setFactorSolverType("mumps")
but didn’t know how to do exactly… And I also wonder if there is another way to improve the code.
import dolfinx.fem
import dolfinx.fem.petsc
import dolfinx.io
import gmsh
import mpi4py.MPI
import numpy as np
import petsc4py.PETSc
import ufl
import viskex
import multiphenicsx.fem
import multiphenicsx.fem.petsc
n = 300
(x_min, x_max) = (0, 8)
mesh = dolfinx.mesh.create_interval(mpi4py.MPI.COMM_WORLD, n, [x_min, x_max])
tdim = mesh.topology.dim
fdim = tdim - 1
facet_left = dolfinx.mesh.locate_entities_boundary(
mesh, fdim, lambda x: np.isclose(x[0], x_min)
)
boundaries = [(1, lambda x: np.isclose(x[0], x_min)),
(2, lambda x: np.isclose(x[0], x_max))]
facet_indices, facet_markers = [], []
for (marker, locator) in boundaries:
facets = dolfinx.mesh.locate_entities(mesh, fdim, locator)
facet_indices.append(facets)
facet_markers.append(np.full_like(facets, marker))
facet_indices = np.hstack(facet_indices).astype(np.int32)
facet_markers = np.hstack(facet_markers).astype(np.int32)
facet_tag = dolfinx.mesh.meshtags(mesh, fdim,
facet_indices, facet_markers)
mesh.topology.create_connectivity(fdim, tdim)
ds = ufl.Measure("ds", domain=mesh, subdomain_data=facet_tag)
U1_element = ufl.FiniteElement("Lagrange", mesh.ufl_cell(), 2)
U2_element = ufl.FiniteElement("Lagrange", mesh.ufl_cell(), 2)
def run_monolithic():
U_element = ufl.MixedElement(U1_element, U2_element)
U = dolfinx.fem.functionspace(mesh, U_element)
du1u2 = ufl.TrialFunction(U)
u1u2 = dolfinx.fem.Function(U)
(u1, u2) = ufl.split(u1u2)
v1v2 = ufl.TestFunction(U)
(v1, v2) = ufl.split(v1v2)
u1u2.x.array[:] = 2 * np.random.rand(1,1)[0][0]
F = u1 * v1 * ds(2) - 2 * v1 * ds(1) - u1 * v1.dx(0) * ufl.dx - u2 * v1 * ufl.dx\
+ (u2.dx(0) + u1 - 5 * (1 - u1**2) * u2) * v2 * ufl.dx
J = ufl.derivative(F, u1u2, du1u2)
u2_left = dolfinx.fem.Function(U.sub(1).collapse()[0])
u2_left.x.array[:] = 0
bdofs_U2 = dolfinx.fem.locate_dofs_topological(
(U.sub(1), U.sub(1).collapse()[0]), fdim, facet_left)
left_bc_u2 = dolfinx.fem.dirichletbc(u2_left, bdofs_U2, U.sub(1))
bc = [left_bc_u2]
class coupled2_1(object):
def __init__(
self, F, J, solution, bcs, P=None
):
self._F = dolfinx.fem.form(F)
self._J = dolfinx.fem.form(J)
self._obj_vec = dolfinx.fem.petsc.create_vector(self._F)
self._solution = solution
self._bcs = bcs
self._P = P
def create_snes_solution(self):
x = self._solution.vector.copy()
with x.localForm() as _x, self._solution.vector.localForm() as _solution:
_x[:] = _solution
return x
def update_solution(self, x):
x.ghostUpdate(addv=petsc4py.PETSc.InsertMode.INSERT, mode=petsc4py.PETSc.ScatterMode.FORWARD)
with x.localForm() as _x, self._solution.vector.localForm() as _solution:
_solution[:] = _x
def obj(
self, snes, x
):
self.F(snes, x, self._obj_vec)
return self._obj_vec.norm()
def F(
self, snes, x, F_vec
):
self.update_solution(x)
with F_vec.localForm() as F_vec_local:
F_vec_local.set(0.0)
dolfinx.fem.petsc.assemble_vector(F_vec, self._F)
dolfinx.fem.apply_lifting(F_vec, [self._J], [self._bcs], x0=[x], scale=-1.0)
F_vec.ghostUpdate(addv=petsc4py.PETSc.InsertMode.ADD, mode=petsc4py.PETSc.ScatterMode.REVERSE)
dolfinx.fem.set_bc(F_vec, self._bcs, x, -1.0)
def J(
self, snes, x, J_mat, P_mat
):
J_mat.zeroEntries()
dolfinx.fem.petsc.assemble_matrix(
J_mat, self._J, self._bcs, diagonal=1.0
)
J_mat.assemble()
if self._P is not None:
P_mat.zeroEntries()
dolfinx.fem.petsc.assemble_matrix(
P_mat, self._P, self._bcs, diagonal=1.0
)
P_mat.assemble()
problem = coupled2_1(F, J, u1u2, bc)
F_vec = dolfinx.fem.petsc.create_vector(problem._F)
J_mat = dolfinx.fem.petsc.create_matrix(problem._J)
snes = petsc4py.PETSc.SNES().create(mesh.comm)
snes.setTolerances(max_it=200)
snes.getKSP().setType("preonly")
snes.getKSP().getPC().setType("lu")
snes.getKSP().getPC().setFactorSolverType("mumps")
snes.setObjective(problem.obj)
snes.setFunction(problem.F, F_vec)
snes.setJacobian(problem.J, J=J_mat, P=None)
snes.setMonitor(lambda _, it, residual: print(it, residual))
u1u2_copy = problem.create_snes_solution()
snes.solve(None, u1u2_copy)
problem.update_solution(u1u2_copy)
u1u2_copy.destroy()
snes.destroy()
return u1u2
u1u2_m = run_monolithic()
(u1_m, u2_m) = (u1u2_m.sub(0).collapse(), u1u2_m.sub(1).collapse())
viskex.dolfinx.plot_scalar_field(u1_m, "u1_m")
viskex.dolfinx.plot_scalar_field(u2_m, "u2_m")