Hi Community,
I am solving the 2D linear Stokes-equations with periodic boundary conditions (from here) for the pressure and all velocity components in x- and y-direction using dolfinx_mpc (version 0.6.1).
Problem description: The domain is a unit square with an inclusion in the center that has a high dynamic viscosity (rigid particle), so the fluid has to flow around this particle. Therefore, the velocities at the interface between the matrix and the particle are set to zero (non-slip boundary conditions). In order to make the solution unique, the pressures at the corners are set to zero (because of the periodic boundary conditions).
Direct Solver: The solution is verified for computing the linear system of equations with a direct solver. The implementation is correct.
Iterative Solver: I would like to use an iterative solver for upcoming 3D simulations (according to this forum post), but the solution is not periodic anymore, because the solution at two edges are strange (see the attached figure). Since the solution for the direct solver is correct, I assume that there is something wrong in the implementation for the iterative solver. Do you have an idea what the problem may be?
Thank you very much!
Here is the MWE:
import gmsh
import dolfinx
import dolfinx_mpc
from dolfinx.fem import FunctionSpace, VectorFunctionSpace, TensorFunctionSpace, Function, Constant, Expression, assemble_scalar, form
from dolfinx.io import XDMFFile
from dolfinx.mesh import locate_entities_boundary, meshtags
from dolfinx.io.gmshio import model_to_mesh
from ufl import grad, div, inner, Measure, TrialFunctions, TestFunctions, FiniteElement, VectorElement
import ufl
from petsc4py import PETSc
import numpy as np
from mpi4py import MPI
# MPI initialization
comm = MPI.COMM_WORLD
rank = MPI.COMM_WORLD.rank
#############################################
# Mesh generation #
#############################################
gmsh.initialize()
L = 1 # length of rectangle
f = 0.3 # volume fraction of inclusion
r = np.sqrt(f*L**2/np.pi) # radius of inclusion
gdim = 2 # Geometric dimension of the mesh
fdim = gdim - 1 # facet dimension
if rank == 0:
inclusion = gmsh.model.occ.addDisk(L / 2, L / 2, 0, r, r)
matrix = gmsh.model.occ.addRectangle(0,0,0, L, L)
whole_domain = gmsh.model.occ.fragment([(2, matrix)], [(2, inclusion)])
gmsh.model.occ.synchronize()
matrix_physical = gmsh.model.addPhysicalGroup(gdim, [matrix], tag = 1)
inclusion_physical = gmsh.model.addPhysicalGroup(gdim, [inclusion], tag = 2)
# Get the interface elements between rectangle and circle and tag
interface_elements = gmsh.model.getBoundary([(gdim, inclusion_physical)])
interface_physical = gmsh.model.addPhysicalGroup(fdim, (5, 5), tag = 9)
background_surfaces = []
inclusion_surfaces = []
for domain in whole_domain[0]:
com = gmsh.model.occ.getCenterOfMass(domain[0], domain[1])
mass = gmsh.model.occ.getMass(domain[0], domain[1])
if np.isclose(mass, np.pi * (r ** 2)): # identify inclusion by its mass
inclusion_surfaces.append(domain)
else:
background_surfaces.append(domain)
gmsh.model.mesh.field.add("Distance", 1)
edges = gmsh.model.getBoundary(inclusion_surfaces, oriented=False)
gmsh.model.mesh.field.setNumbers(1, "EdgesList", [e[1] for e in edges])
gmsh.model.mesh.field.add("Threshold", 2)
gmsh.model.mesh.field.setNumber(2, "IField", 1)
gmsh.model.mesh.field.setNumber(2, "LcMin", 0.01)
gmsh.model.mesh.field.setNumber(2, "LcMax", 0.03)
gmsh.model.mesh.field.setNumber(2, "DistMin", 0.01)
gmsh.model.mesh.field.setNumber(2, "DistMax", 0.1)
gmsh.model.mesh.field.setAsBackgroundMesh(2)
gmsh.option.setNumber("Mesh.Algorithm", 6)
gmsh.model.mesh.generate(gdim)
domain, ct, ft = model_to_mesh(gmsh.model, comm, rank, gdim=gdim)
gmsh.finalize()
#############################################
# Mesh generation finished #
#############################################
dx = Measure('dx', domain=domain, subdomain_data=ct)
num_cells = domain.topology.index_map(domain.topology.dim).size_global
# elements and funcionspace
###########################
# Taylor-Hood elements and Mixed Functionspace
P1 = FiniteElement("CG", domain.ufl_cell(), 1)
P2 = VectorElement("CG", domain.ufl_cell(), 2)
TH = ufl.MixedElement([P2, P1])
V = FunctionSpace(domain, TH)
# define function, trial function and test function
(ve, pr) = TrialFunctions(V)
(dve, dpr) = TestFunctions(V)
# define material parameter
S = FunctionSpace(domain, ("DG", 0))
vis = Function(S)
for i in range(num_cells):
if ct.values[i] == 1: # matrix
vis.x.array[i] = 1
elif ct.values[i] == 2: # inclusions
vis.x.array[i] = 1e+5
vis.x.scatter_forward()
# boundary conditions
#####################
## set all fluid velocities at the interface to zero (no slip boundary condition)
# create collapsed functionspace
V_coll0, _ = V.sub(0).collapse()
v_bc = Function(V_coll0)
v_bc.x.set(0)
v_bc.x.scatter_forward()
# dofs of interface and dirichlet bcs
dofs_v_interf = dolfinx.fem.locate_dofs_topological((V.sub(0), V_coll0) , ft.dim, ft.find(9)) # interface tag: 9
bcs_v_interf = dolfinx.fem.dirichletbc(v_bc, dofs_v_interf, V.sub(0))
## set pressures at the corners to zero
def boundary_node(x):
return np.logical_or(
np.logical_and(
np.isclose(x[0], 0.),
np.logical_or(
np.isclose(x[1], .0),
np.isclose(x[1], L),
)
),
np.logical_and(
np.isclose(x[0], L),
np.logical_or(
np.isclose(x[1], 0.),
np.isclose(x[1], L),
)
),
)
boundary_facets = locate_entities_boundary(domain, 0, boundary_node) # it is zero dimension for a point
dofs_corners = dolfinx.fem.locate_dofs_topological(V.sub(1), 0, boundary_facets) # it is zero dimension for a point
bc_corners = dolfinx.fem.dirichletbc(PETSc.ScalarType(0.0), dofs_corners, V.sub(1))
bcs = [bcs_v_interf, bc_corners]
## periodic boundary conditions
pbc_directions = []
pbc_slave_tags = []
pbc_is_slave = []
pbc_is_master = []
pbc_meshtags = []
pbc_slave_to_master_maps = []
mpc = dolfinx_mpc.MultiPointConstraint(V)
def generate_pbc_slave_to_master_map(i):
def pbc_slave_to_master_map(x):
out_x = x.copy()
out_x[i] = x[i] - L
return out_x
return pbc_slave_to_master_map
def generate_pbc_is_slave(i):
return lambda x: np.isclose(x[i], L)
def generate_pbc_is_master(i):
return lambda x: np.isclose(x[i], 0.0)
for i in range(gdim):
pbc_directions.append(i)
pbc_slave_tags.append(i + 2)
pbc_is_slave.append(generate_pbc_is_slave(i))
pbc_is_master.append(generate_pbc_is_master(i))
pbc_slave_to_master_maps.append(generate_pbc_slave_to_master_map(i))
facets = locate_entities_boundary(domain, fdim, pbc_is_slave[-1])
arg_sort = np.argsort(facets)
pbc_meshtags.append(meshtags(domain,
fdim,
facets[arg_sort],
np.full(len(facets), pbc_slave_tags[-1], dtype=np.int32)))
N_pbc = len(pbc_directions)
for i in range(N_pbc):
if N_pbc > 1:
def pbc_slave_to_master_map(x):
out_x = pbc_slave_to_master_maps[i](x)
idx = pbc_is_slave[(i + 1) % N_pbc](x)
out_x[pbc_directions[i]][idx] = np.nan
print(out_x[pbc_directions[i]][idx])
print(idx)
return out_x
else:
pbc_slave_to_master_map = pbc_slave_to_master_maps[i]
for ij in range(gdim):
mpc.create_periodic_constraint_topological(V.sub(0).sub(ij), pbc_meshtags[i],
pbc_slave_tags[i],
pbc_slave_to_master_map,
bcs)
mpc.create_periodic_constraint_topological(V.sub(1), pbc_meshtags[i],
pbc_slave_tags[i],
pbc_slave_to_master_map,
bcs)
if len(pbc_directions) > 1:
# Map intersection(slaves_x, slaves_y) to intersection(masters_x, masters_y),
# i.e. map the slave dof at (1, 1)/(1,1,1) to the master dof at (0, 0)/(0,0,0)
def pbc_slave_to_master_map(x):
out_x = x.copy()
out_x[0] = x[0] - domain.geometry.x[:, 0].max()
out_x[1] = x[1] - domain.geometry.x[:, 1].max()
idx = np.logical_and(pbc_is_slave[0](x), pbc_is_slave[1](x))
out_x[0][~idx] = np.nan
out_x[1][~idx] = np.nan
return out_x
for ij in range(gdim):
mpc.create_periodic_constraint_topological(V.sub(0).sub(ij), pbc_meshtags[1],
pbc_slave_tags[1],
pbc_slave_to_master_map,
bcs)
mpc.create_periodic_constraint_topological(V.sub(1), pbc_meshtags[1],
pbc_slave_tags[1],
pbc_slave_to_master_map,
bcs)
mpc.finalize()
# Variational problem
#####################
f_constant = Constant(domain, PETSc.ScalarType((1,0)))
## weak formulation
a = vis * inner(grad(ve), grad(dve)) * dx + pr * div(dve) * dx + div(ve) * dpr * dx
L = inner(f_constant, dve) * dx
## solver - can be easily switched
solver = ["direct", "iterative"][1]
func_sol = Function(V)
if solver == "direct":
problem = dolfinx_mpc.LinearProblem(a, L, mpc, bcs=bcs, petsc_options={
"ksp_type": "preonly", "pc_type": "lu", "pc_factor_mat_solver_type": "mumps"}) # define linear problem
func_sol = problem.solve()
elif solver == "iterative":
a_f = form(a)
L_f = form(L)
## iterative solver
A = dolfinx_mpc.assemble_matrix(a_f, mpc, bcs=bcs)
A.assemble()
b = dolfinx_mpc.assemble_vector(L_f, mpc)
dolfinx_mpc.apply_lifting(b, [a_f], [bcs], mpc)
b.ghostUpdate(addv=PETSc.InsertMode.ADD, mode=PETSc.ScatterMode.REVERSE)
dolfinx.fem.petsc.set_bc(b, bcs)
b.ghostUpdate(addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD)
# Preconditioner matrix form
Pf = dolfinx.fem.form(ufl.inner(ufl.grad(ve), ufl.grad(dve)) * dx + pr * dpr * dx)
P = dolfinx_mpc.assemble_matrix(Pf, mpc, bcs=bcs)
P.assemble()
ksp = PETSc.KSP().create(domain.comm)
ksp.setOperators(A, P)
ksp.setType(PETSc.KSP.Type.MINRES)
pc = ksp.getPC()
pc.setType("hypre")
pc.setHYPREType("boomeramg")
pc.setUp()
ksp.solve(b, func_sol.vector)
ve, pr = func_sol.sub(0).collapse(), func_sol.sub(1).collapse()
# intialize files for the results
file_results1 = XDMFFile(domain.comm, "results_2D_"+solver+"/pressure.xdmf", "w")
file_results1.write_mesh(domain)
file_results4 = XDMFFile(domain.comm, "results_2D_"+solver+"/velocity.xdmf", "w")
file_results4.write_mesh(domain)
file_results1.write_function(pr)
file_results4.write_function(ve)
file_results1.close()
file_results4.close()