Attribute _blocks lost when using SNES

Hello,

Below is a MWE I created to show an issue with PETSc.SNES on a mixed formulation. The vector F_vec below is created with F_vec = dolfinx.fem.petsc.create_vector(F) and therefore receives the attribute _blocks. However this attribute is somehow lost. An error is then raised in function_fbecause dolfinx.fem.petsc.assemble_vector requires the attribute.

import dolfinx
import scifem
import ufl
from mpi4py import MPI
from petsc4py import PETSc

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

Vspace  = dolfinx.fem.functionspace(msh, ("DG", 2))
f = dolfinx.fem.Function(Vspace)
f_test = ufl.TestFunction(Vspace)

Rspace = scifem.create_real_functionspace(msh)
r = dolfinx.fem.Function(Rspace)
r_test = ufl.TestFunction(Rspace)

form = ufl.inner(f, f_test) * ufl.dx
form_r = ufl.inner(r, r_test) * ufl.dx
J = dolfinx.fem.form(ufl.derivative(form, f))

form_block = [form, form_r]

Jff = ufl.derivative(form, f)
Jfr = ufl.derivative(form, r)
Jrf = ufl.derivative(form_r, f)
Jrr = ufl.derivative(form_r, r)
J_block = [[Jff, Jfr], [Jrf, Jrr]]

F = dolfinx.fem.form(form_block)
J = dolfinx.fem.form(J_block)

F_vec = dolfinx.fem.petsc.create_vector(F)
J_mat = dolfinx.fem.petsc.assemble_matrix(J)

def function_f(solver, x, F_vec) -> None:
    """Assemble the residual."""
    print('Attribute blocks ',F_vec.getAttr('_blocks'))
    dolfinx.fem.petsc.assemble_vector(F_vec, F)
    F_vec.set(1.0)
    return
def function_j(solver, x, J, P):
    """Assemble the Jacobian matrix."""
    J.zeroEntries()        # ensure it's zero
    J.assemble()
    J.shift(1.0)           # A = A + 1 * I
    return
solution = dolfinx.fem.petsc.create_vector(F)
snes = PETSc.SNES().create(msh.comm)
snes.setFunction(function_f, F_vec)
snes.setJacobian(function_j, J=J_mat, P=None)
snes.setObjective(lambda x,y : 1.0)
snes.solve(None, solution)

The output is:

Attribute blocks  ((0, 192, 193), (193, 193, 193))
Attribute blocks  None
Traceback (most recent call last):
  File "petsc4py/PETSc/PETSc.pyx", line 348, in petsc4py.PETSc.PetscPythonErrorHandler
  File "petsc4py/PETSc/PETSc.pyx", line 348, in petsc4py.PETSc.PetscPythonErrorHandler
  File "petsc4py/PETSc/PETSc.pyx", line 348, in petsc4py.PETSc.PetscPythonErrorHandler
  [Previous line repeated 2 more times]
  File "petsc4py/PETSc/petscsnes.pxi", line 334, in petsc4py.PETSc.SNES_Function
  File "/stck/fpascal/Python/Code/FENICS/PbBlocks/issue.py", line 38, in function_f
    dolfinx.fem.petsc.assemble_vector(F_vec, F)
  File "/opt/tools/python/3.10.8-gnu831/lib/python3.10/functools.py", line 889, in wrapper
    return dispatch(args[0].__class__)(*args, **kw)
  File "/opt/Softwares/FE_libraries/dolfinx/Dist/real/lib/python3.10/site-packages/dolfinx/fem/petsc.py", line 339, in _
    offset0, offset1 = b.getAttr("_blocks")
TypeError: cannot unpack non-iterable NoneType object

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/stck/fpascal/Python/Code/FENICS/PbBlocks/issue.py", line 52, in <module>
    snes.solve(None, solution)
  File "petsc4py/PETSc/SNES.pyx", line 1724, in petsc4py.PETSc.SNES.solve
petsc4py.PETSc.Error: error code -1
[0] SNESSolve() at /opt/Softwares/petsc/src/snes/interface/snes.c:4846
[0] SNESSolve_NEWTONLS() at /opt/Softwares/petsc/src/snes/impls/ls/ls.c:235
[0] SNESLineSearchApply() at /opt/Softwares/petsc/src/snes/linesearch/interface/linesearch.c:643
[0] SNESLineSearchApply_BT() at /opt/Softwares/petsc/src/snes/linesearch/impls/bt/linesearchbt.c:333
[0] SNESComputeFunction() at /opt/Softwares/petsc/src/snes/interface/snes.c:2490

The two first lines show that the attribute is removed at some point.

PS : The ugly fix is to add this code at the beginning of function_f:

 F_vec.setAttr('_blocks', solver.getFunction()[0].getAttr('_blocks'))

But maybe we don’t want to have to use this fix.

This is a subtle issue with PETSc, as SNES internally creates some new working vectors, but it doesn’t copy over attributes set to the original vector.
As seen in the assemble_residual used in DOLFINx, we have an internal function that assigns block data:

which calls:

Thank you for your answer. Please note that I created as well an issue on the PETSc gitlab . In particular I posted a MWE 100% PETSc where a vector attribute is not deleted (see below). What is the difference with the dolfinx implementation given above where the attribute disappears?

The MWE:

from petsc4py import PETSc
from mpi4py import MPI

ts = PETSc.TS().create()

F_vec = PETSc.Vec().create(comm=PETSc.COMM_SELF)
F_vec.setSizes(10)
F_vec.setUp()
F_vec.setAttr('attribute', 'value')
assert(F_vec.getAttr('attribute') == 'value')

mat = PETSc.Mat().create()
mat.setSizes((10,10))
mat.setFromOptions()

def evalFunction(ts, t, x, xdot, f):
    assert(f.getAttr('attribute') == 'value')
    return
def evalJacobian(ts, t, x, xdot, a, A, B):
    return

ts.setIFunction(evalFunction, F_vec)
ts.setIJacobian(evalJacobian, mat)

ts.setEquationType(PETSc.TS.EquationType.IMPLICIT)
ts.setProblemType(PETSc.TS.ProblemType.NONLINEAR)

x = PETSc.Vec().create(comm=PETSc.COMM_SELF)
x.setSizes(10)
x.setUp()
ts.setSolution(x)

ts.setTime(0.0)
ts.setTimeStep(1.0)
ts.setMaxSteps(5)

opts = PETSc.Options()
opts['ts_adapt_type'] = 'basic'
opts['ksp_type'] = 'preonly'
opts['pc_type'] = 'lu'
ts.setFromOptions()
ts.solve()

@dokken This time the issue is not with PETSc making duplicates, because the vector returned by TSGetIFunction() should be the same as the one originally set. The only way this changes is someone is calling TSSetIFunction() in the interim. I want him to run in the debugger and get a stack trace when this happens. Could you do that?

Hi Matt,
Isn’t the difference between the two examples here that one uses PETSc TS while the other one uses PETSc SNES?

Please note that I get this issue both with SNES and TS.

Do you have a pure PETSc snes problem that you could run where the attribute is copied?

And could you do as Matt suggests:

In general DOLFINx doesn’t do anything magical when adding the attributes:

I know that @garth implemented the “_blocks” attribute so maybe he can supply more information/context

I created a MWE 100% PETSc that triggers the problem:

from petsc4py import PETSc
from mpi4py import MPI

snes = PETSc.SNES().create()

F_vec = PETSc.Vec().create(comm=PETSc.COMM_SELF)
F_vec.setSizes(10)
F_vec.setUp()
F_vec.setAttr('_blocks', 'value')
assert(F_vec.getAttr('_blocks') == 'value')

mat = PETSc.Mat().create()
mat.setSizes((10,10))
mat.setFromOptions()

def evalFunction(snes, x, f):
    print(f.getAttr('_blocks'),'/',snes.getFunction()[0].getAttr('_blocks'))
    f.set(1.0)
    return
def evalJacobian(snes, x, A, B):
    A.assemble()
    A.shift(1.0)
    return

snes.setFunction(evalFunction, F_vec)
snes.setJacobian(evalJacobian, mat)


x = PETSc.Vec().create(comm=PETSc.COMM_SELF)
x.setSizes(10)
x.setUp()
snes.setSolution(x)


snes.setIterationNumber(1)

opts = PETSc.Options()
opts['ksp_type'] = 'preonly'
opts['pc_type'] = 'lu'
snes.setFromOptions()
snes.solve()

The first lines of the output (see the print in evalFunction) are:

value / value
None / value
None / value

This shows that the attribute _blocks “disappeared”.

Could you setattr for x as well?

The script below shows that _blocks is removed from x as well.

from petsc4py import PETSc
from mpi4py import MPI

snes = PETSc.SNES().create()

F_vec = PETSc.Vec().create(comm=PETSc.COMM_SELF)
F_vec.setSizes(10)
F_vec.setUp()
F_vec.setAttr('_blocks', 'value')
assert(F_vec.getAttr('_blocks') == 'value')

mat = PETSc.Mat().create()
mat.setSizes((10,10))
mat.setFromOptions()

def evalFunction(snes, x, f):
    print(f.getAttr('_blocks'),'/',snes.getFunction()[0].getAttr('_blocks'),'/',x.getAttr('_blocks'))
    f.set(1.0)
    return
def evalJacobian(snes, x, A, B):
    A.assemble()
    A.shift(1.0)
    return

snes.setFunction(evalFunction, F_vec)
snes.setJacobian(evalJacobian, mat)


x = PETSc.Vec().create(comm=PETSc.COMM_SELF)
x.setSizes(10)
x.setUp()
x.setAttr('_blocks', 'value')
snes.setSolution(x)


snes.setIterationNumber(1)

opts = PETSc.Options()
opts['ksp_type'] = 'preonly'
opts['pc_type'] = 'lu'
snes.setFromOptions()
snes.solve()

This is more along the lines of what I’ve seen with the attributes in PETSc. @Matthew_Knepley any suggestions?

EDIT: I’ve tried to adapt DOLFINx to instead use the args and kwargs positional input arguments, rather than using functools.partial, which obfuscates the input arguments, at: Use PETSc CTX for SNES solver by jorgensd · Pull Request #4139 · FEniCS/dolfinx · GitHub

The attributes are Python-only. I am now discussing it with Lisandro.

That’s fine by me, I kind of like the option of sending args and kwargs to the function instead, as done in the PR to DOLFINx. I wasn’t aware that it was an option.