I get zero errors in the matrix when I assemble matrix. These rows correspond to the Lagrange multiplier variables over facets. Is this typical for assembling matrix blocks over multiple subdomains?
The error is of the form The 72-th row of A is exactly zero
My MWE to reproduce the error is below (and code runs on main branch of FEniCSx)
#!/usr/bin/env python
import argparse
import json
import os
import timeit
import dolfinx
import numpy as np
import petsc4py
import ufl
import warnings
from dolfinx import cpp, default_scalar_type, fem, graph, io, mesh, nls, plot
from dolfinx.fem import petsc
from dolfinx.io import gmshio, VTXWriter
from dolfinx.nls import petsc as petsc_nls
from mpi4py import MPI
from petsc4py import PETSc
from ufl import (Circumradius, FacetNormal, SpatialCoordinate, TrialFunction, TestFunction,
dot, div, dx, ds, dS, grad, inner, grad, avg, jump)
warnings.simplefilter('ignore')
dtype = PETSc.ScalarType
faraday_const = 96485
D = 1e-15
kappa = 0.1
if __name__ == '__main__':
phase_1 = 1
phase_2 = 2
left = 1
right = 4
middle = 7
external = 8
insulated_phase_1 = 9
insulated_phase_2 = 10
output_meshfile = 'mesh.msh'
comm = MPI.COMM_WORLD
partitioner = mesh.create_cell_partitioner(mesh.GhostMode.shared_facet)
domain, ct, ft = gmshio.read_from_msh(output_meshfile, comm, partitioner=partitioner)
tdim = domain.topology.dim
fdim = tdim - 1
domain.topology.create_connectivity(tdim, fdim)
ft_imap = domain.topology.index_map(fdim)
num_facets = ft_imap.size_local + ft_imap.num_ghosts
indices = np.arange(0, num_facets, dtype=np.int32)
values = np.zeros(indices.shape, dtype=np.int32) # all facets are tagged with zero
values[ft.indices] = ft.values
ft = mesh.meshtags(domain, fdim, indices, values)
# create submesh
submesh, sub_to_parent, vertex_map, geom_map = mesh.create_submesh(
domain, tdim, ct.find(phase_2)
)
# transfer tags from parent to submesh
tdim = domain.topology.dim
fdim = tdim - 1
c_imap = domain.topology.index_map(tdim)
num_cells = c_imap.size_local + c_imap.num_ghosts
mesh_to_submesh = np.full(num_cells, -1, dtype=np.int32)
mesh_to_submesh[sub_to_parent] = np.arange(len(sub_to_parent), dtype=np.int32)
entity_maps = {submesh: mesh_to_submesh}
# interface mesh
submeshf, subf_to_parent, _, _ = mesh.create_submesh(
domain, fdim, ft.find(middle)
)
mesh_to_facet_mesh = np.full(num_facets, -1, dtype=np.int32)
mesh_to_facet_mesh[subf_to_parent] = np.arange(len(subf_to_parent), dtype=np.int32)
entity_maps[submeshf] = mesh_to_facet_mesh
# integration measures
dx = ufl.Measure("dx", domain=domain, subdomain_data=ct)
ds = ufl.Measure("ds", domain=domain, subdomain_data=ft)
dx_f = ufl.Measure('dx', domain=submeshf)
ds_f = ufl.Measure('ds', domain=submeshf)
# Function Spaces
k = 3
V0 = fem.functionspace(domain, ("CG", k)) # whole domain
V1 = fem.functionspace(submesh, ("CG", k)) # phase 2 subdomain
V2 = fem.functionspace(submeshf, ("CG", k)) # middle boundary facets
u = ufl.TrialFunction(V0)
v = ufl.TestFunction(V0)
c = ufl.TrialFunction(V1)
q = ufl.TestFunction(V1)
lamda = ufl.TrialFunction(V2)
eta = ufl.TestFunction(V2)
# initial condition
c0 = fem.Function(V1)
c0.interpolate(lambda x: 0.5 * 27000 + x[0] - x[0])
n = ufl.FacetNormal(domain)
nc = ufl.FacetNormal(submesh)
x = ufl.SpatialCoordinate(domain)
# constants
dt_ = 1e-3
dt = fem.Constant(submesh, dtype(dt_))
f0 = fem.Constant(domain, dtype(0))
f1 = fem.Constant(submesh, dtype(0))
f2 = fem.Constant(submeshf, dtype(0))
g0 = fem.Constant(domain, dtype(0))
g1 = fem.Constant(submesh, dtype(0))
u_left = fem.Function(V0)
with u_left.vector.localForm() as u0_loc:
u0_loc.set(0)
u_right = fem.Function(V0)
with u_right.vector.localForm() as u1_loc:
u1_loc.set(3.5)
# variational formulation
a00 = fem.form(kappa*inner(grad(u), grad(v))*dx + inner(-kappa*grad(u), n) * v * ds)
a02 = fem.form(inner(lamda, kappa/faraday_const*inner(grad(v), n)) * ds(middle), entity_maps=entity_maps)
a11 = fem.form(inner(c, q)*dx(phase_2) - dt*inner(D*grad(c), grad(q))*dx(middle) - dt * D * inner(inner(grad(c), n), q) * ds(middle), entity_maps=entity_maps)
a12 = fem.form(- inner(lamda, D * inner(grad(q), n)) * ds(middle), entity_maps=entity_maps)
a20 = fem.form(inner(eta, kappa/faraday_const * inner(grad(u), n)) * ds(middle), entity_maps=entity_maps)
a21 = fem.form(- inner(eta, D * inner(grad(c), n)) * ds(middle), entity_maps=entity_maps)
left_bc = fem.dirichletbc(u_left, fem.locate_dofs_topological(V0, 1, ft.find(left)))
right_bc = fem.dirichletbc(u_right, fem.locate_dofs_topological(V0, 1, ft.find(right)))
a = [[a00, None, a02], [None, a11, a12], [a20, a21, None]]
L0_ = inner(f0, v)*dx
L0_ += inner(g0, v) * ds(insulated_phase_1)
L0_ += inner(g0, v) * ds(insulated_phase_2)
L0 = fem.form(L0_)
L1_ = dt * inner(f1, q) * dx(phase_2)
L1_ += - inner(c0, q)*dx(phase_2)
L1_ += dt * inner(g1, q) * ds(insulated_phase_2)
L1_ += dt * inner(g1, q) * ds(right)
L1 = fem.form(L1_, entity_maps=entity_maps)
L2 = fem.form(inner(f2, eta) * ds_f, entity_maps=entity_maps)
L = [L0, L1, L2]
A = fem.petsc.assemble_matrix_block(a, bcs=[left_bc, right_bc])
A.assemble()
b = fem.petsc.assemble_vector_block(L, a, bcs=[left_bc, right_bc])
# A.view()
# solve coupled
TIME = 1 * dt_
t = 0
vol = comm.allreduce(fem.assemble_scalar(fem.form(1 * dx(phase_2), entity_maps=entity_maps)), op=MPI.SUM)
while t < TIME:
print(f"Time: {t:.3f}")
t += dt_
ksp = PETSc.KSP().create(comm)
ksp.setMonitor(lambda _, it, residual: print(it, residual))
ksp.setOperators(A)
opts = PETSc.Options()
ksp.setType("preonly")
ksp.getPC().setType("lu")
ksp.getPC().setFactorSolverType("superlu_dist")
# Compute solution
x = A.createVecLeft()
ksp.solve(b, x)
# Recover solution
u, c, lmbda = fem.Function(V0), fem.Function(V1), fem.Function(V2)
offset = V0.dofmap.index_map.size_local * V0.dofmap.index_map_bs
offset2 = V1.dofmap.index_map.size_local * V1.dofmap.index_map_bs
offset3 = V2.dofmap.index_map.size_local * V2.dofmap.index_map_bs
u.x.array[:offset] = x.array_r[:offset]
u.x.scatter_forward()
c.x.array[:offset2] = x.array_r[offset:offset+offset2]
c.x.scatter_forward()
lmbda.x.array[:(len(x.array_r) - offset - offset2)] = x.array_r[offset + offset2:]
lmbda.x.scatter_forward()
I_middle_1 = comm.allreduce(fem.assemble_scalar(fem.form(inner(-(kappa * grad(u)), n) * ds(middle))), op=MPI.SUM)
I_middle_2 = comm.allreduce(fem.assemble_scalar(fem.form(inner(-faraday_const * D * grad(c), n) * ds(middle),entity_maps=entity_maps)), op=MPI.SUM)
print(f"I_middle_1: {I_middle_1:.2e}, I_middle_2: {I_middle_2:.2e}")
I generate my mesh from
#!/usr/bin/env python3
import os
import gmsh
def create_mesh(output_meshfile):
micron = 1e-6
resolution = 5 * micron
LX = 150 * micron
LY = 40 * micron
points = [
(0, 0, 0),
(0.5 * LX, 0, 0),
(LX, 0, 0),
(LX, LY, 0),
(0.5 * LX, LY, 0),
(0, LY, 0),
]
gpoints = []
lines = []
gmsh.initialize()
gmsh.model.add('two-subdomains')
gmsh.option.setNumber("Mesh.MeshSizeExtendFromBoundary", 0)
gmsh.option.setNumber("Mesh.MeshSizeFromPoints", 0)
gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", 1)
for idx, p in enumerate(points):
gpoints.append(
gmsh.model.occ.addPoint(*p)
)
gmsh.model.occ.synchronize()
gmsh.model.occ.synchronize()
for idx in range(0, len(points)-1):
lines.append(
gmsh.model.occ.addLine(gpoints[idx], gpoints[idx+1])
)
lines.append(
gmsh.model.occ.addLine(gpoints[-1], gpoints[0])
)
lines.append(
gmsh.model.occ.addLine(gpoints[1], gpoints[4])
)
phase_1 = 1
phase_2 = 2
left = 1
bottom_left = 2
bottom_right = 3
right = 4
top_right = 5
top_left = 6
middle = 7
external = 8
insulated_phase_1 = 9
insulated_phase_2 = 10
labels = {
"phase_1": phase_1,
"phase_2": phase_2,
"left": left,
"bottom_left": bottom_left,
"bottom_right": bottom_right,
"right": right,
"top_right": top_right,
"top_left": top_left,
"middle": middle,
"external": external,
"insulated_phase_1": insulated_phase_1,
"insulated_phase_2": insulated_phase_2,
}
gmsh.model.occ.synchronize()
gmsh.model.addPhysicalGroup(1, [lines[-2]], left, "left")
gmsh.model.addPhysicalGroup(1, [lines[2]], right, "right")
gmsh.model.addPhysicalGroup(1, [lines[idx] for idx in [-1]], middle, "middle")
# gmsh.model.addPhysicalGroup(1, [lines[idx] for idx in [0]], bottom_left, "bottom left")
# gmsh.model.addPhysicalGroup(1, [lines[idx] for idx in [4]], top_left, "top left")
# gmsh.model.addPhysicalGroup(1, [lines[idx] for idx in [1]], bottom_right, "bottom right")
# gmsh.model.addPhysicalGroup(1, [lines[idx] for idx in [3]], top_right, "top right")
gmsh.model.addPhysicalGroup(1, [lines[idx] for idx in [0, 4]], insulated_phase_1, "insulated phase 1")
gmsh.model.addPhysicalGroup(1, [lines[idx] for idx in [1, 3]], insulated_phase_2, "insulated phase 2")
gmsh.model.addPhysicalGroup(1, lines[:-1], external, "external")
gmsh.model.occ.synchronize()
se_loop = gmsh.model.occ.addCurveLoop([lines[idx] for idx in [0, 6, 4, 5]])
pe_loop = gmsh.model.occ.addCurveLoop([lines[idx] for idx in [1, 2, 3, 6]])
gmsh.model.occ.synchronize()
se_phase = gmsh.model.occ.addPlaneSurface([se_loop])
pe_phase = gmsh.model.occ.addPlaneSurface([pe_loop])
gmsh.model.occ.synchronize()
gmsh.model.addPhysicalGroup(2, [se_phase], phase_1, "phase 1")
gmsh.model.addPhysicalGroup(2, [pe_phase], phase_2, "phase 2")
gmsh.model.occ.synchronize()
gmsh.model.mesh.generate(2)
gmsh.write(output_meshfile)
gmsh.finalize()
return labels
if __name__ == '__main__':
create_mesh("mesh.msh")