Setting neumann and dirichlet boundary conditions in 2D with assemble vector

I’m running into problems setting up the bilinear form of the problem. Assembling a vector with both dirichlet and neumann boundary conditions throws the error below:

Traceback (most recent call last):
  File "demo_poisson.py", line 53, in <module>
    b = assemble_vector(L)
  File "/usr/lib/python3.8/functools.py", line 875, in wrapper
    return dispatch(args[0].__class__)(*args, **kw)
  File "/home/ubuntu/.local/lib/python3.8/site-packages/dolfinx/fem/assemble.py", line 124, in assemble_vector
    b = la.create_petsc_vector(L.function_spaces[0].dofmap.index_map,
IndexError: list index out of range
Traceback (most recent call last):
  File "demo_poisson.py", line 53, in <module>
    b = assemble_vector(L)
  File "/usr/lib/python3.8/functools.py", line 875, in wrapper
    return dispatch(args[0].__class__)(*args, **kw)
  File "/home/ubuntu/.local/lib/python3.8/site-packages/dolfinx/fem/assemble.py", line 124, in assemble_vector
    b = la.create_petsc_vector(L.function_spaces[0].dofmap.index_map,
IndexError: list index out of range

The following is the code set-up and the last line is the one that throws and error

V = VectorFunctionSpace(mesh, ("Lagrange", 1))
u, v = ufl.TrialFunction(V), ufl.TestFunction(V)
x = ufl.SpatialCoordinate(mesh)

f = ufl.as_vector((0.0, 0.0))  # source term
g = ufl.as_vector((0.0, 0.0))  # neumann boundary condition of zero flux

a = form(inner(grad(u), grad(v)) * dx(x))
L = form(inner(f, v) * dx(x) + inner(g, v) * ds(mesh))

A = assemble_matrix(a, bcs=[x0bc, x1bc])
A.assemble()
b = assemble_vector(L)

Can you provide a MWE?

Yes.

import dolfinx
import numpy as np
import ufl
from dolfinx.fem import (apply_lifting, assemble_matrix, assemble_vector, Constant, dirichletbc, Expression,
                         form, Function, FunctionSpace, locate_dofs_topological, LinearProblem, set_bc,
                         VectorFunctionSpace
                         )
from dolfinx.io import XDMFFile
from dolfinx.mesh import locate_entities_boundary, create_rectangle, CellType, GhostMode
from mpi4py import MPI
from petsc4py import PETSc
from petsc4py.PETSc import ScalarType
from ufl import ds, dx, grad, inner, dot


mesh = create_rectangle(MPI.COMM_WORLD, [np.array([0.0, 0.0]),
                                  np.array([10, 10])], [10, 10],
                 CellType.triangle, GhostMode.shared_facet)
tdim = mesh.topology.dim
fdim = tdim - 1

V = VectorFunctionSpace(mesh, ("Lagrange", 1))

# Define boundary condition on x = 0 or x = 1
u0 = Function(V)
with u0.vector.localForm() as u0_loc:
    u0_loc.set(1)
u1 = Function(V)
with u1.vector.localForm() as u1_loc:
    u1_loc.set(0)
x0facets = locate_entities_boundary(mesh, fdim,
                                    lambda x: np.isclose(x[0], 0.0))
x1facets = locate_entities_boundary(mesh, fdim,
                                    lambda x: np.isclose(x[0], 10.0))
x0bc = dirichletbc(u0, locate_dofs_topological(V, fdim, x0facets))
x1bc = dirichletbc(u1, locate_dofs_topological(V, fdim, x1facets))

# Define variational problem
u, v = ufl.TrialFunction(V), ufl.TestFunction(V)
x = ufl.SpatialCoordinate(mesh)

f = ufl.as_vector((0.0, 0.0))
g = ufl.as_vector((0.0, 0.0))

a = form(inner(grad(u), grad(v)) * dx(x))
L = form(inner(f, v) * dx(x) + inner(g, v) * ds(mesh))

A = assemble_matrix(a, bcs=[x0bc, x1bc])
A.assemble()

b = assemble_vector(L)
apply_lifting(b, [a], bcs=[[x0bc, x1bc]])
b.ghostUpdate(addv=PETSc.InsertMode.ADD, mode=PETSc.ScatterMode.REVERSE)
set_bc(b, [x0bc, x1bc])

# Set solver options
opts = PETSc.Options()
opts["ksp_type"] = "cg"
opts["ksp_rtol"] = 1.0e-10
opts["pc_type"] = "gamg"

# Use Chebyshev smoothing for multigrid
opts["mg_levels_ksp_type"] = "chebyshev"
opts["mg_levels_pc_type"] = "jacobi"

# Improve estimate of eigenvalues for Chebyshev smoothing
opts["mg_levels_esteig_ksp_type"] = "cg"
opts["mg_levels_ksp_chebyshev_esteig_steps"] = 20

# Create PETSc Krylov solver and turn convergence monitoring on
solver = PETSc.KSP().create(mesh.comm)
solver.setFromOptions()

# Set matrix operator
solver.setOperators(A)

uh = Function(V)

# Set a monitor, solve linear system, and dispay the solver configuration
# solver.setMonitor(lambda _, its, rnorm: print(f"Iteration: {its}, rel. residual: {rnorm}"))
solver.solve(b, uh.vector)
# solver.view()

uh.x.scatter_forward()

# Save solution in XDMF format
with XDMFFile(MPI.COMM_WORLD, "potential.xdmf", "w") as file:
    file.write_mesh(mesh)
    file.write_function(uh)

I don’t really understand what your intent is with the terms dx(x) and ds(mesh). dx(x) doesn’t make sense, and perhaps you needed the latter to actually give you a non-zero form (since UFL will optimise out zeros unless wrapped in a constant)?

I’m running dolfinx@main so there are some minor changes to my import statements, but the code is largely the same. Note I use Constants for f and g and I changed the ds and dx terms.

import dolfinx
import numpy as np
import ufl
from dolfinx.fem import (apply_lifting, Constant, dirichletbc, Expression,
                         form, Function, FunctionSpace, locate_dofs_topological, set_bc,
                         VectorFunctionSpace
                         )
from dolfinx.fem.petsc import LinearProblem, assemble_matrix, assemble_vector
from dolfinx.io import XDMFFile
from dolfinx.mesh import locate_entities_boundary, create_rectangle, CellType, GhostMode
from mpi4py import MPI
from petsc4py import PETSc
from petsc4py.PETSc import ScalarType
from ufl import ds, dx, grad, inner, dot


mesh = create_rectangle(MPI.COMM_WORLD, [np.array([0.0, 0.0]),
                                  np.array([10, 10])], [10, 10],
                 CellType.triangle, GhostMode.shared_facet)
tdim = mesh.topology.dim
fdim = tdim - 1

V = VectorFunctionSpace(mesh, ("Lagrange", 1))

# Define boundary condition on x = 0 or x = 1
u0 = Function(V)
with u0.vector.localForm() as u0_loc:
    u0_loc.set(1)
u1 = Function(V)
with u1.vector.localForm() as u1_loc:
    u1_loc.set(0)
x0facets = locate_entities_boundary(mesh, fdim,
                                    lambda x: np.isclose(x[0], 0.0))
x1facets = locate_entities_boundary(mesh, fdim,
                                    lambda x: np.isclose(x[0], 10.0))
x0bc = dirichletbc(u0, locate_dofs_topological(V, fdim, x0facets))
x1bc = dirichletbc(u1, locate_dofs_topological(V, fdim, x1facets))

# Define variational problem
u, v = ufl.TrialFunction(V), ufl.TestFunction(V)
x = ufl.SpatialCoordinate(mesh)

f = dolfinx.fem.Constant(mesh, PETSc.ScalarType((0.0, 0.0)))
g = dolfinx.fem.Constant(mesh, PETSc.ScalarType((0.0, 0.0)))

a = form(inner(grad(u), grad(v)) * dx)
L = form(inner(f, v) * dx + inner(g, v) * ds)

A = assemble_matrix(a, bcs=[x0bc, x1bc])
A.assemble()

b = assemble_vector(L)
apply_lifting(b, [a], bcs=[[x0bc, x1bc]])
b.ghostUpdate(addv=PETSc.InsertMode.ADD, mode=PETSc.ScatterMode.REVERSE)
set_bc(b, [x0bc, x1bc])

# Set solver options
opts = PETSc.Options()
opts["ksp_type"] = "cg"
opts["ksp_rtol"] = 1.0e-10
opts["pc_type"] = "gamg"

# Use Chebyshev smoothing for multigrid
opts["mg_levels_ksp_type"] = "chebyshev"
opts["mg_levels_pc_type"] = "jacobi"

# Improve estimate of eigenvalues for Chebyshev smoothing
opts["mg_levels_esteig_ksp_type"] = "cg"
opts["mg_levels_ksp_chebyshev_esteig_steps"] = 20

# Create PETSc Krylov solver and turn convergence monitoring on
solver = PETSc.KSP().create(mesh.comm)
solver.setFromOptions()

# Set matrix operator
solver.setOperators(A)

uh = Function(V)

# Set a monitor, solve linear system, and dispay the solver configuration
# solver.setMonitor(lambda _, its, rnorm: print(f"Iteration: {its}, rel. residual: {rnorm}"))
solver.solve(b, uh.vector)
# solver.view()

uh.x.scatter_forward()

# Save solution in XDMF format
with XDMFFile(MPI.COMM_WORLD, "potential.xdmf", "w") as file:
    file.write_mesh(mesh)
    file.write_function(uh)

which gives me this:

image

This is neat, thanks. I suppose my problem is that the form of the source and flux terms requires the domains to be passed into the dx and ds terms. It did not occur to me that I can pass a vector into the PETSc.ScalarType.

See this demo for an example of using subdomain data in ufl.Measures. Specifically, this followed by this.

2 Likes