Adding bounds to a phase field problem

Hi! I am trying to implement a monolithic solver with energy based line search for a phase field ductile fracture model. The weak forms of the problem are:

V = fem.functionspace(dmesh, (“Lagrange”, 1, (3,)))
Q = fem.functionspace(dmesh, (“Lagrange”, 1,))

v_u = ufl.TestFunction(V)

v_d = ufl.TestFunction(Q)

du = ufl.TrialFunction(V)
dd = ufl.TrialFunction(Q)

d = fem.Function(Q)

R_u = ufl.inner(epsilon(v_u), (sig)) * dx

R_d = -2p0*((1-d)**(2p0-1))*H(psi_elas, Hold)*v_d*dx + 0.5*(G_cr/l0)*ufl.inner(d,v_d)*dx + 2*G_cr*l0*ufl.dot(ufl.grad(d),ufl.grad(v_d)) *dx

J_uu = ufl.inner(epsilon(du), sigma_tang(epsilon(v_u), d, p0)) * dx

J_ud = ufl.derivative(R_u, d, dd)
J_du = ufl.derivative(R_d, u, du)
J_dd = ufl.derivative(R_d, d, dd)

Res = [R_u, R_d]

Jac = [
[J_uu, J_ud],
[J_du, J_dd]
]

For the monolithic solver, I modified the scifem’s blocked Newton solver to account for the plasticity related terms and to implement the line search algorithm.

The routine produces valid results, until the phase field variable “d” attains a value greater than 1 (fully broken phase), in which case the solver produces nan values. I suspect this term in the phase field weak form

-2p0*((1-d)**(2p0-1))

causes these nan values when d > 1.

Is there any way I can add bounds to the phase field problem, such that the solution remains bounded within [0, 1] in this particular solver setting? I know there is an option within the SNES solver to add bounds to the problem, but I don’t know if that can be applied within this monolithic solver.

Thanks for the help!

I would suggest either:

  1. Switching to SNES (blocked systems are easily supported in DOLFINx v0.10.0, see for instance:dolfinx/python/test/unit/fem/test_petsc_nonlinear_assembler.py at v0.10.0.post3 · FEniCS/dolfinx · GitHub
  2. Enforce the constraint using LVPP (disclaimer: I am one of the authors of the following paper): Redirecting with code available at: GitHub - METHODS-Group/ProximalGalerkin: This repository contains examples of the proximal Galerkin finite element method and other proximal numerical methods derived from the LVPP algorithm (example on setting bounds to snes is shown for non-blocked problem in ProximalGalerkin/examples/01_obstacle_problem/obstacle_snes.py at main · METHODS-Group/ProximalGalerkin · GitHub). I would use dolfinx.fem.petsc.assign([min_u, min_d], min_udx_petsc_vec) and similarly for the max value to set the bounds for the blocked system (use dolfinx.fem.petsc.create_vector(..., kind="mpi") to create the correct block structure
1 Like

I am trying to set bounds for my non-linear blocked system following your suggestion (2) with the following code -

### UNKNOWNS ###

u_blocked = (fC, fL)

### BOUNDS ###

lbC, ubC = fem.Function(Cc), fem.Function(Cc)
lbC.x.array[:], ubC.x.array[:] = 0.0, +PETSc.INFINITY

lbL, ubL = fem.Function(Ll), fem.Function(Ll)
lbL.x.array[:], ubL.x.array[:] = -PETSc.INFINITY, +PETSc.INFINITY

lbN = create_vector([Cc, Ll], kind="mpi")
ubN = create_vector([Cc, Ll], kind="mpi")

fem.petsc.assign([lbC, lbL], lbN)
fem.petsc.assign([ubC, ubL], ubN)

problem = fem.petsc.NonlinearProblem(
    F_blocked,
    u_blocked,
    bcs=bcs_cnc,
    J=J_blocked,
    petsc_options=petsc_options,
    petsc_options_prefix="pnp_",
    kind="nest",
)
problem.solver.setVariableBounds(lbN, ubN)

This is my result -

0 SNES Function norm 1.285270931327e-10
    Residual norms for pnp_ solve.
    0 KSP Residual norm 1.285270931327e-10
    1 KSP Residual norm 2.828853424757e-11
    2 KSP Residual norm 2.823105884811e-11
    3 KSP Residual norm 2.563929901551e-11
    4 KSP Residual norm 1.616059349057e-11
    5 KSP Residual norm 1.495681929485e-11
    6 KSP Residual norm 1.492806332161e-11
    7 KSP Residual norm 1.182993000176e-11
    8 KSP Residual norm 1.162158860000e-11
    9 KSP Residual norm 1.136108451505e-11
   10 KSP Residual norm 1.112580745234e-11
   11 KSP Residual norm 1.112580725851e-11
   12 KSP Residual norm 1.112580562764e-11
   13 KSP Residual norm 1.087420805554e-11
   14 KSP Residual norm 1.080106092639e-11
    ...
   766 KSP Residual norm 1.004550263003e-13
   767 KSP Residual norm 1.003663385664e-13
   768 KSP Residual norm 1.002778853151e-13
   769 KSP Residual norm 1.001896655149e-13
   770 KSP Residual norm 1.001016781408e-13
   771 KSP Residual norm 1.000139221739e-13
   772 KSP Residual norm 9.992639660172e-14
1 SNES Function norm 3.770447572879e-08

The code breaks here with the following error -

Error: error code 75
[0] SNESSolve() at /usr/local/petsc/src/snes/interface/snes.c:4906
[0] SNESSolve_VINEWTONRSLS() at /usr/local/petsc/src/snes/impls/vi/rs/virs.c:381
[0] MatCreateSubMatrix() at /usr/local/petsc/src/mat/interface/matrix.c:8575
[0] MatCreateSubMatrix_Nest() at /usr/local/petsc/src/mat/impls/nest/matnest.c:712
[0] MatNestFindSubMat() at /usr/local/petsc/src/mat/impls/nest/matnest.c:699
[0] Arguments are incompatible
[0] Could not find index set

I have tried create_vector with kind="nest" as well but it yields the same result. I am using dolfinx v0.10.0-r1. Additionally, I would like to add that the weak forms were written following your tutorial on using a Schur complement preconditioner for a mixed Poisson problem.

Please provide a reproducible example. This is not reproducible.
Here is a reproducible example:

from mpi4py import MPI
import dolfinx.fem.petsc
import ufl
import numpy as np

mesh = dolfinx.mesh.create_unit_square(MPI.COMM_WORLD, 4, 4)

V0 = dolfinx.fem.functionspace(mesh, ("Lagrange", 2, (2,)))
V1 = dolfinx.fem.functionspace(mesh, ("Lagrange", 1))

W = ufl.MixedFunctionSpace(V0, V1)
u = dolfinx.fem.Function(V0)
p = dolfinx.fem.Function(V1)
v,q = ufl.TestFunctions(W)

F = ufl.inner(ufl.grad(u), ufl.grad(v)) * ufl.dx + p * ufl.div(v) * ufl.dx + q * ufl.div(u) * ufl.dx + dolfinx.fem.Constant(mesh, 0.0)*p*q*ufl.dx
J = ufl.derivative(F, (u,p), ufl.TrialFunctions(W))



x = ufl.SpatialCoordinate(mesh)
f = ufl.as_vector((ufl.sin(x[0]*ufl.pi), x[1]))
F -= ufl.inner(f,v )* ufl.dx

F_blocked = ufl.extract_blocks(F)
kind = "mpi"
lower_bounds = dolfinx.fem.petsc.create_vector([V0, V1], kind=kind)
upper_bounds = dolfinx.fem.petsc.create_vector([V0, V1], kind=kind)

u_min = dolfinx.fem.Function(V0)
u_max = dolfinx.fem.Function(V0)
p_min = dolfinx.fem.Function(V1)
p_max = dolfinx.fem.Function(V1)
u_min.x.array[:] = -np.inf
u_max.x.array[:] = np.inf
p_min.x.array[:] = -10
p_max.x.array[:] = 100

dolfinx.fem.petsc.assign([u_min, p_min], lower_bounds)
dolfinx.fem.petsc.assign([u_max, p_max], upper_bounds)

bc = dolfinx.fem.dirichletbc(dolfinx.fem.Function(V0), dolfinx.fem.locate_dofs_geometrical(V0, lambda x: np.isclose(x[0], 0.0)))


snes_options={
        "snes_type": "vinewtonssls",
        "snes_linesearch_type": "basic",
        "snes_monitor": None,
        "ksp_type": "preonly",
        "pc_type": "lu",
        "pc_factor_mat_solver_type": "mumps",
        "snes_max_it": 1000,
        "snes_atol": 1e-8,
        "snes_rtol": 1e-8,
        "snes_stol": 1e-8,
        "snes_error_if_not_converged": True,
        "ksp_error_if_not_converged": True,
    }
problem = dolfinx.fem.petsc.NonlinearProblem(
    F_blocked,
    u = [u,p],
    bcs=[bc],
    petsc_options=snes_options,
    petsc_options_prefix="pnp_",
    kind=kind,
)
problem.solver.setVariableBounds(lower_bounds, upper_bounds)
problem.solve()


with dolfinx.io.VTXWriter(mesh.comm, "solution_u.bp", [u]) as writer:
    writer.write(0.0)


with dolfinx.io.VTXWriter(mesh.comm, "solution_p.bp", [p]) as writer:
    writer.write(0.0)

Apologies for vague initial message. Here is a reproducible example following the one you provided here and taking elements from my code which leads to the error.
The problem is a constrained PDE solve using a Lagrange multiplier. I am considering the [c1, c2] as one block and [lmb] as the other.

from mpi4py import MPI
import dolfinx.fem.petsc
import basix
import ufl
import numpy as np

mesh = dolfinx.mesh.create_unit_square(MPI.COMM_WORLD, 4, 4)

elem_order = 1
fin_elem = basix.ufl.element("Lagrange", mesh.basix_cell(), degree=elem_order)
cnc_elem = basix.ufl.mixed_element([fin_elem, fin_elem])

Ss = dolfinx.fem.functionspace(mesh, fin_elem)
Cc = dolfinx.fem.functionspace(mesh, cnc_elem)
Ww = ufl.MixedFunctionSpace(Cc, Ss)

cnc = dolfinx.fem.Function(Cc)
lmb = dolfinx.fem.Function(Ss)

uC, uS = ufl.TrialFunctions(Ww)
vC, vS = ufl.TestFunctions(Ww)

c1, c2 = ufl.split(cnc)
v_c1, v_c2 = ufl.split(vC)

F_c1 = ufl.inner(ufl.grad(c1), ufl.grad(v_c1)) * ufl.dx \
       + lmb * v_c1 * ufl.dx
F_c2 = ufl.inner(ufl.grad(c2), ufl.grad(v_c2)) * ufl.dx \
       + lmb * v_c2 * ufl.dx
F_lmb = (c1 + c2) * vS * ufl.dx
F_cnc = F_c1 + F_c2
F_all = F_cnc + F_lmb
F_blocked = ufl.extract_blocks(F_all)

J_all = ufl.derivative(F_cnc, cnc, uC) + ufl.derivative(F_cnc, lmb, uS) \
        + ufl.derivative(F_lmb, cnc, uC) + ufl.derivative(F_lmb, lmb, uS)
J_blocked = ufl.extract_blocks(J_all)

kind = "nest"
lower_bounds = dolfinx.fem.petsc.create_vector([Cc, Ss], kind=kind)
upper_bounds = dolfinx.fem.petsc.create_vector([Cc, Ss], kind=kind)

cnc_min = dolfinx.fem.Function(Cc)
cnc_max = dolfinx.fem.Function(Cc)
lmb_min = dolfinx.fem.Function(Ss)
lmb_max = dolfinx.fem.Function(Ss)
cnc_min.x.array[:] = 0.0
cnc_max.x.array[:] = np.inf
lmb_min.x.array[:] = -np.inf
lmb_max.x.array[:] = np.inf

dolfinx.fem.petsc.assign([cnc_min, lmb_min], lower_bounds)
dolfinx.fem.petsc.assign([cnc_max, lmb_max], upper_bounds)

bdry_values = [600.0, -600.0]

def left_boundary(x):
    return np.isclose(x[0], 0.0)

bcs_cnc = []
for i, val in enumerate(bdry_values):
    Vsub, submap = Cc.sub(i).collapse()
    dofs_sub = dolfinx.fem.locate_dofs_geometrical(Vsub, left_boundary)
    dofs_parent = submap[dofs_sub]
    bc = dolfinx.fem.dirichletbc(val, dofs_parent, Cc.sub(i))
    bcs_cnc.append(bc)

snes_options = {
    "snes_type": "vinewtonrsls",
    "snes_linesearch_type": "basic",
    # "snes_linesearch_type": "bt",
    "snes_force_iteration": "",  # ensures one SNES iteration per call
    "snes_atol": 1e-12,
    "snes_rtol": 1e-15,
    "snes_stol": 0.0,
    "snes_max_it": 50,
    "snes_monitor": "",

    "ksp_error_if_not_converged": True,
    "ksp_type": "gmres",
    "ksp_gmres_restart": 1000,
    "ksp_rtol": 1e-15,
    "ksp_atol": 3.2e-13,
    "ksp_max_it": 10000,
    "ksp_monitor": "",
    "ksp_norm_type": "unpreconditioned",
    # "ksp_monitor_true_residual": "relative",
    # "ksp_view": "",
    # "pc_view": "",
    # "ksp_diagonal_scale": True,
    # "ksp_diagonal_scale_fix": True,

    "pc_type": "fieldsplit",
    "pc_fieldsplit_type": "schur",
    "pc_fieldsplit_schur_fact_type": "full",
    "pc_fieldsplit_schur_precondition": "full",

    # ---- Block 0 ----
    "fieldsplit_0_ksp_type": "preonly",
    # "fieldsplit_0_ksp_norm_type": "unpreconditioned",
    # "fieldsplit_0_ksp_monitor_true_residual": None,
    "fieldsplit_0_pc_type": "lu",
    "fieldsplit_0_pc_factor_mat_solver_type": "mumps",
    # "fieldsplit_0_pc_factor_shift_type": "NONZERO",
    # "fieldsplit_0_pc_factor_shift_amount": 1e-13,

    # ---- Block 1 ----
    "fieldsplit_1_ksp_type": "preonly",
    # "fieldsplit_1_ksp_norm_type": "unpreconditioned",
    # "fieldsplit_1_ksp_monitor_true_residual": None,
    "fieldsplit_1_pc_type": "lu",
    # "fieldsplit_1_pc_factor_mat_solver_type": "mumps",
    # "fieldsplit_1_pc_factor_shift_type": "NONZERO",
    # "fieldsplit_1_pc_factor_shift_amount": 1e-13,
}

problem = dolfinx.fem.petsc.NonlinearProblem(
    F_blocked,
    u = [cnc,lmb],
    bcs=bcs_cnc,
    petsc_options=snes_options,
    petsc_options_prefix="pnp_",
    kind=kind,
)

problem.solver.setVariableBounds(lower_bounds, upper_bounds)
problem.solve()

This produces the same error but the initial SNES norm is quite large.

0 SNES Function norm 1.749285568454e+03
---------------------------------------------------------------------------
Error                                     Traceback (most recent call last)
Cell In[7], line 142
    132 problem = dolfinx.fem.petsc.NonlinearProblem(
    133     F_blocked,
    134     u = [cnc,lmb],
   (...)    138     kind=kind,
    139 )
    141 problem.solver.setVariableBounds(lower_bounds, upper_bounds)
--> 142 problem.solve()

File /usr/local/dolfinx-real/lib/python3.12/dist-packages/dolfinx/fem/petsc.py:1351, in NonlinearProblem.solve(self)
   1348 assign(self.u, self.x)
   1350 # Solve problem
-> 1351 self.solver.solve(None, self.x)
   1352 dolfinx.la.petsc._ghost_update(self.x, PETSc.InsertMode.INSERT, PETSc.ScatterMode.FORWARD)  # type: ignore[attr-defined]
   1354 # Copy solution back to function

File petsc4py/PETSc/SNES.pyx:1738, in petsc4py.PETSc.SNES.solve()

Error: error code 75
[0] SNESSolve() at /usr/local/petsc/src/snes/interface/snes.c:4906
[0] SNESSolve_VINEWTONRSLS() at /usr/local/petsc/src/snes/impls/vi/rs/virs.c:381
[0] MatCreateSubMatrix() at /usr/local/petsc/src/mat/interface/matrix.c:8575
[0] MatCreateSubMatrix_Nest() at /usr/local/petsc/src/mat/impls/nest/matnest.c:712
[0] MatNestFindSubMat() at /usr/local/petsc/src/mat/impls/nest/matnest.c:699
[0] Arguments are incompatible
[0] Could not find index set

For my problem the KSP for the first SNES iteration converges and the code breaks at the second SNES iteration. I would like to add that if we remove variable bounds then the error does not occur.

This seems like something weird within PETSc, as it seems like it cannot find the indexsets that were initially set to the “nest” jacobian.

Could you please suggest an alternate way? Additionally, I have tried the reproducible example you provided with kind=“nest” and that works.

I would need to think about this, as making a small example to figure out if this is a bug in PETSc or not requires some work.

Okay, thank you so much for giving the time.

I believe this is a bug in PETSc (report filed at: NEST combined with vinewtonrsls and bounds yields Could not find index set (#1831) · Issues · PETSc / petsc · GitLab).
Consider this hand-coded 1D finite element formulation of Poisson:

# Hand-coded formulation of the Poisson equation with homogeneous Dirichlet boundary conditions
# using PETSc
# Author: Jørgen S. Dokken
# SPDX-License-Identifier: MIT

from mpi4py import MPI
from petsc4py import PETSc
import numpy as np

# Define source term and boundary condition


def f(x_ref, a, b):
    return np.pi**2 * np.sin(np.pi * (a + x_ref * (b - a)))


def g(x_ref, a, b):
    # NOTE: The implementation below only works for homogeneous BCs
    return 0.0


def phi_0(x_ref):
    return 1 - x_ref


def phi_1(x_ref):
    return x_ref


def dphi_0(x_ref, b, a):
    return -1 / (b - a)


def dphi_1(x_ref, b, a):
    return 1 / (b - a)


# Equal spacing option
# x_start = 0.0
# x_end = 1.0
# x = np.linspace(x_start, x_end, N, endpoint=True)
# N = 15

x = np.array([0.0, 0.05, 0.1, 0.3, 0.5, 0.6, 0.8, 1.0])
N = len(x)

# Create dofmap
dofmap = np.repeat(np.arange(N), 2).reshape(-1, 2).astype(np.int32)
dofmap[:, 1] += 1


# Create submatrices
A00 = PETSc.Mat().createAIJ([N, N])
A11 = PETSc.Mat().createAIJ([N, N])
A = PETSc.Mat().createNest([[A00, None], [None, A11]])

# Create subvectors
b0 = PETSc.Vec().createSeq(N)
b1 = PETSc.Vec().createSeq(N)
b = PETSc.Vec().createNest([b0, b1])


compute_interval_jacobian = lambda x_ref, a, b: b - a


def fill_matrix(mat):
    """
    Compute stiffness matrix with a one-point quadrature
    """
    mat.zeroEntries()
    p_ref = 0.5
    for cell in range(N - 1):
        a = x[cell]
        b = x[cell + 1]
        J_e = compute_interval_jacobian(p_ref, a, b)
        Ke = np.array(
            [
                [
                    dphi_0(p_ref, b, a) * dphi_0(p_ref, b, a) * J_e,
                    dphi_0(p_ref, b, a) * dphi_1(p_ref, b, a) * J_e,
                ],
                [
                    dphi_1(p_ref, b, a) * dphi_0(p_ref, b, a) * J_e,
                    dphi_1(p_ref, b, a) * dphi_1(p_ref, b, a) * J_e,
                ],
            ],
            dtype=np.float64,
        )
        if cell == 0:
            Ke[0, :] = 0.0
            Ke[0, 0] = 1.0
        elif cell == N - 2:
            Ke[1, :] = 0.0
            Ke[1, 1] = 1.0
        rows = dofmap[cell, :]

        mat.setValues(rows, rows, Ke, addv=True)
    mat.assemble()


def fill_vector(vec):
    """
    Compute load vector with a one-point quadrature
    """
    with vec.localForm() as local_vec:
        local_vec.set(0.0)

    for cell in range(N - 1):
        a = x[cell]
        b = x[cell + 1]
        J_e = compute_interval_jacobian(0.0, a, b)
        fe = np.array(
            [
                f(0.5, a, b) * phi_0(0.5) * J_e,
                f(0.5, a, b) * phi_1(0.5) * J_e,
            ],
            dtype=np.float64,
        )
        if cell == 0:
            fe[0] = g(0.0, a, b)
        elif cell == N - 2:
            fe[1] = g(1.0, a, b)
        rows = dofmap[cell, :]

        vec.setValues(rows, fe, addv=True)

    vec.assemble()


# Fill A and b as it only has to be done once
fill_matrix(A00)
fill_matrix(A11)
A.assemble()

fill_vector(b0)
fill_vector(b1)
b.assemble()


def assemble_jacobian(_snes, x, J, P):
    """Wrapper for snes"""
    J.zeroEntries()

    J00 = J.getNestSubMatrix(0, 0)
    J11 = J.getNestSubMatrix(1, 1)
    A00.copy(J00)
    A11.copy(J11)

    J.assemble()


# Working arrays for residual
tmp0 = b0.duplicate()
tmp1 = b1.duplicate()


def assemble_residual(_snes, x, F):
    """Wrapper for snes"""
    F0, F1 = F.getNestSubVecs()
    x0, x1 = x.getNestSubVecs()
    with F0.localForm() as local_F0, F1.localForm() as local_F1:
        local_F0.set(0.0)
        local_F1.set(0.0)

    b0.copy(F0)
    b1.copy(F1)
    F0.scale(-1)
    F1.scale(-1)
    with tmp0.localForm() as local_tmp0, tmp1.localForm() as local_tmp1:
        local_tmp0.set(0.0)
        local_tmp1.set(0.0)
    with tmp0.localForm() as local_tmp0:
        local_tmp0.set(0.0)

    A00.mult(x0, tmp0)
    A11.mult(x1, tmp1)
    F0.axpy(1.0, tmp0)
    F1.axpy(1.0, tmp1)
    F0.array_w[0] = 0.0
    F0.array_w[-1] = 0.0
    F1.array_w[0] = 0.0
    F1.array_w[-1] = 0.0
    F.assemble()


snes = PETSc.SNES().create(MPI.COMM_SELF)
F = b.copy()
J = A.copy()
snes.setFunction(assemble_residual, F)
snes.setJacobian(assemble_jacobian, J, None)

solver_lower = b.copy()
for sub_vec in solver_lower.getNestSubVecs():
    with sub_vec.localForm() as local_sub_vec:
        local_sub_vec.set(-0.5)
solver_upper = b.copy()
for sub_vec in solver_upper.getNestSubVecs():
    with sub_vec.localForm() as local_sub_vec:
        local_sub_vec.set(0.5)
snes.setVariableBounds(solver_lower, solver_upper)

petsc_options = {
    "snes_type": "vinewtonrsls",
    # "snes_type": "newtonls",
    "ksp_type": "preonly",
    "pc_type": "lu",
    "snes_linesearch_type": "none",
    "pc_factor_mat_solver_type": "mumps",
    "snes_monitor": None,
    "snes_error_if_not_converged": True,
}

opts = PETSc.Options()
for key, val in petsc_options.items():
    opts.setValue(key, val)
snes.setFromOptions()

nest_IS = J.getNestISs()
fieldsplit_IS = tuple([(f"{i}", IS) for i, IS in enumerate(nest_IS[0])])
snes.getKSP().getPC().setFieldSplitIS(*fieldsplit_IS)
snes.setErrorIfNotConverged(True)

uh = b.duplicate()
snes.solve(None, uh)

# Direct solver check
ksp = PETSc.KSP().create(MPI.COMM_SELF)
ksp.setOperators(A)
ksp.setType("preonly")
ksp.getPC().setType("lu")
ksp.getPC().setFactorSolverType("mumps")
ksp.setErrorIfNotConverged(True)
ksp.setMonitor(
    lambda ksp, its, rnorm: print(f"KSP Iteration {its}, Residual norm: {rnorm}")
)
ksp.solve(b, uh)

import matplotlib.pyplot as plt


uh0, uh1 = uh.getNestSubVecs()
plt.plot(x, uh0.array, label="Numerical Solution (0)")
plt.plot(x, uh1.array, "bs", label="Numerical Solution (1)")


x_ref = np.linspace(0.0, 1.0, 100, endpoint=True)
plt.plot(x_ref, np.sin(np.pi * x_ref), label="Exact")
plt.legend()
plt.show()

This works with KSP, with SNES and “newtonls” and SNES with “vinewtonrsls” without bounds.
Adding the bounds yields:

 0 SNES Function norm 3.904873750087e+00
  1 SNES Function norm 2.902005876076e+00
Traceback (most recent call last):
  File "/home/dokken/Documents/src/debug.py", line 224, in <module>
    snes.solve(None, uh)
    ~~~~~~~~~~^^^^^^^^^^
  File "petsc4py/PETSc/SNES.pyx", line 1738, in petsc4py.PETSc.SNES.solve
petsc4py.PETSc.Error: error code 75
[0] SNESSolve() at /home/conda/feedstock_root/build_artifacts/bld/rattler-build_petsc_1755537683/work/src/snes/interface/snes.c:4846
[0] SNESSolve_VINEWTONRSLS() at /home/conda/feedstock_root/build_artifacts/bld/rattler-build_petsc_1755537683/work/src/snes/impls/vi/rs/virs.c:381
[0] MatCreateSubMatrix() at /home/conda/feedstock_root/build_artifacts/bld/rattler-build_petsc_1755537683/work/src/mat/interface/matrix.c:8456
[0] MatCreateSubMatrix_Nest() at /home/conda/feedstock_root/build_artifacts/bld/rattler-build_petsc_1755537683/work/src/mat/impls/nest/matnest.c:712
[0] MatNestFindSubMat() at /home/conda/feedstock_root/build_artifacts/bld/rattler-build_petsc_1755537683/work/src/mat/impls/nest/matnest.c:699
[0] Arguments are incompatible
[0] Could not find index set

Firstly, thank you so much for raising this issue with PETSc. I followed the link to the PETSc thread and saw that a dev has replied that it is an expected error due to the limitations of a function in its existing state. Could you please suggest a different approach to handle bounding differently through FEniCSx?

It might be that the extended matrix of @jackhale and Martin Rehor at Rafinex-external-RIFLE / FEniCSx-pctools · GitLab (create splittable matrix) can handle these kinds of problems.
I’m not sure though.

The other alternative is to consider

Thank you very much for your suggestions. I will look into them.