Apply_lifting in the context of a block system

Hello,

I wonder whether it is possible to use apply_lifting in conjunction with a block system to enforce Dirichlet boundary conditions. I illustrated my issue in the following code example:

from mpi4py import MPI
from petsc4py import PETSc
from dolfinx.mesh import create_unit_interval
from dolfinx.fem import functionspace, Function, form, Constant, apply_lifting, set_bc
import dolfinx.fem.petsc as petsc
from basix.ufl import element
from ufl import Measure, TestFunction, inner, grad, derivative


msh = create_unit_interval(MPI.COMM_WORLD, 10)
dx = Measure("dx", domain=msh, metadata={"quadrature_degree":1})
cg1_element = element("Lagrange", msh.basix_cell(), 1)
V = functionspace(msh, cg1_element)
W = functionspace(msh, cg1_element)

v = Function(V)
v_t = Function(V)
v_test = TestFunction(V)
f = Function(V)
v_exact = Function(V)

w = Function(W)
w_t = Function(W)
w_test = TestFunction(W)
g = Function(W)
w_exact = Function(W)

# The following two functions are from
# https://gitlab.com/rafinex-external-rifle/fenicsx-pctools/
def functions_to_vec(u: list[Function], x):
    """Copies functions into block vector."""
    if x.getType() == "nest":
        for i, subvec in enumerate(x.getNestSubVecs()):
            u[i].x.petsc_vec.copy(subvec)
            subvec.ghostUpdate(addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD)
    else:
        offset = 0
        for i in range(len(u)):
            size_local = u[i].x.petsc_vec.getLocalSize()
            with x.localForm() as loc:
                loc.array[offset : offset + size_local] = u[i].vector.array_r
            offset += size_local
            x.ghostUpdate(addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD)


def vec_to_functions(x, u: list[Function]):
    """Copies block vector into functions."""
    if x.getType() == "nest":
        for i, subvec in enumerate(x.getNestSubVecs()):
            subvec.copy(u[i].x.petsc_vec)
            u[i].vector.ghostUpdate(addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD)
    else:
        offset = 0
        for i in range(len(u)):
            size_local = u[i].x.petsc_vec.getLocalSize()
            u[i].x.petsc_vec.array[:] = x.array_r[offset : offset + size_local]
            offset += size_local
            u[i].x.petsc_vec.ghostUpdate(
                addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD
            )


class PETScTSSolver:
    def __init__(self, ts_problem, solution_vars, bcs):
        self.solution_vars = solution_vars
        self.v = solution_vars[0]
        self.w = solution_vars[1]
        self.func_space_v = self.v.ufl_function_space()
        self.func_space_w = self.w.ufl_function_space()
        self.comm = self.func_space_v.mesh.comm
        self.v_t = Function(self.func_space_v)
        self.w_t = Function(self.func_space_w)
        self.sigma_t = Constant(self.func_space_v.mesh, 0.0)
        self.f_v = ts_problem.F_v(self.v, self.v_t, self.w, self.w_t)
        self.f_w = ts_problem.F_w(self.v, self.v_t, self.w, self.w_t)
        self.f_vv = self.sigma_t * ts_problem.dFvdvt(self.v, self.v_t, self.w, self.w_t) + ts_problem.dFvdv(self.v, self.v_t, self.w, self.w_t)
        self.f_vw = self.sigma_t * ts_problem.dFvdwt(self.v, self.v_t, self.w, self.w_t) + ts_problem.dFvdw(self.v, self.v_t, self.w, self.w_t)
        self.f_wv = self.sigma_t * ts_problem.dFwdvt(self.v, self.v_t, self.w, self.w_t) + ts_problem.dFwdv(self.v, self.v_t, self.w, self.w_t)
        self.f_ww = self.sigma_t * ts_problem.dFwdwt(self.v, self.v_t, self.w, self.w_t) + ts_problem.dFwdw(self.v, self.v_t, self.w, self.w_t)
        self.bcs = bcs
        self.ts = PETSc.TS().create(self.comm)


    def evalIFunction(self, ts, t, x, xdot, F):
        with F.localForm() as f_local:
            f_local.set(0.0)

        x.ghostUpdate(addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD)
        vec_to_functions(x, self.solution_vars)
        vec_to_functions(xdot, [self.v_t, self.w_t])


        petsc.assemble_vector_block(F, L=[form(self.f_v), form(self.f_w)], a=[[form(self.f_vv), form(self.f_vw)],[form(self.f_wv), form(self.f_ww)]])

        apply_lifting(F, a=[[form(self.f_vv), form(self.f_vw)],[form(self.f_wv), form(self.f_ww)]], bcs=[self.bcs], x0=x, alpha=-1.0)
        F.ghostUpdate(addv=PETSc.InsertMode.ADD, mode=PETSc.ScatterMode.REVERSE)
        set_bc(F, bcs=self.bcs, x0=x, scale=-1.0)
        F.assemble()

        return True

    def solve(self, t0, dt_init, max_t, max_steps):

        self.ts.setIFunction(self.evalIFunction)
        x0 = petsc.create_vector_block(form([self.f_v, self.f_w]))
        self.ts.setSolution(x0)
        self.ts.setTime(t0)
        self.ts.setTimeStep(dt_init)
        self.ts.setMaxTime(max_t)
        self.ts.setType(PETSc.TS.Type.BDF)
        self.ts.setTolerances(atol=1e-5, rtol=1e-5)
        self.ts.setMaxSteps(max_steps)
        self.ts.solve(x0)

        return True


class WeakFormPDE():
    def F_v(self, v, v_t, w, w_t):
        self.v = v
        self.v_t = v_t
        self.w = w
        self.w_t = w_t

        F_v = self.v_t * v_test * dx + inner(grad(v_test), grad(self.v)) * dx + self.w * v_test * dx

        return  F_v


    def F_w(self, v, v_t, w, w_t):
        self.v = v
        self.v_t = v_t
        self.w = w
        self.w_t = w_t

        F_w = self.w_t * w_test * dx + inner(grad(w_test), grad(self.w)) * dx + self.v * w_test * dx

        return F_w


    def dFvdv(self, v, v_t, w, w_t):
        return derivative(self.F_v(v, v_t, w, w_t), v)

    def dFvdvt(self, v, v_t, w, w_t):
        return derivative(self.F_v(v, v_t, w, w_t), v_t)

    def dFvdw(self, v, v_t, w, w_t):
        return derivative(self.F_v(v, v_t, w, w_t), w)

    def dFvdwt(self, v, v_t, w, w_t):
        return derivative(self.F_v(v, v_t, w, w_t), w_t)

    def dFwdv(self, v, v_t, w, w_t):
        return derivative(self.F_w(v, v_t, w, w_t), v)

    def dFwdvt(self, v, v_t, w, w_t):
        return derivative(self.F_w(v, v_t, w, w_t), v_t)

    def dFwdw(self, v, v_t, w, w_t):
        return derivative(self.F_w(v, v_t, w, w_t), w)

    def dFwdwt(self, v, v_t, w, w_t):
        return derivative(self.F_w(v, v_t, w, w_t), w_t)

Dirichlet_bcs = []

petsc_ts_solver = PETScTSSolver(WeakFormPDE(), [v, w], Dirichlet_bcs)
petsc_ts_solver.solve(t0=0, dt_init=1e-3, max_t=1, max_steps=1e3)

While executing apply_lifting, the above code causes an exception, which I do not understand. When removing the two lines

apply_lifting(F, a=[[form(self.f_vv), form(self.f_vw)],[form(self.f_wv), form(self.f_ww)]], bcs=[self.bcs], x0=x, alpha=-1.0)
set_bc(F, bcs=self.bcs, x0=x, scale=-1.0)

from the code it works. However, then also the lifting procedure is not applied. Any ideas what needs t be changed here?

Thank you for your help.

There was an overhaul (with major improvements) of the assembly functions in Unify assembly functions for PETSc data structures (Python) by garth-wells · Pull Request #3668 · FEniCS/dolfinx · GitHub , so be prepared to change your code when the next dolfinx version will be released.

For the current dolfinx version, you must pass bcs to petsc.assemble_vector_block, see the function signature.

Thanks for the heads up, I’m looking forward to the new version.

Regarding my original problem:

The signature reads

def assemble_vector_block(
    L: list[Form],
    a: list[list[Form]],
    bcs: list[DirichletBC] = []

so adding

petsc.assemble_vector_block(F, L=[form(self.f_v), form(self.f_w)], a=[[form(self.f_vv), form(self.f_vw)],[form(self.f_wv), form(self.f_ww)]], bcs=self.bcs)

does not resolve my issue. Apparently, the error stems from a faulty application of the nested list, which I am trying to pass as a within

apply_lifting(F, a=[[form(self.f_vv), form(self.f_vw)],[form(self.f_wv), form(self.f_ww)]], bcs=[self.bcs], x0=x, alpha=-1.0)

Any ideas how to use apply_lifting correctly in this specific situation?

Thank you.

Lifting on 0.9.0 is part of assemble_vector_block, see the lines:

thus, by modifying the inputs to x0 and alpha in: dolfinx/python/dolfinx/fem/petsc.py at v0.9.0.post1 · FEniCS/dolfinx · GitHub
you should be able to do what you want.

As already stated, this is fully revamped on the main branch.

1 Like