Set LinearProblem with a Power(Coefficient... object

I am trying to implement a projection operator, like the function local_project(v, V) in this notebook.

One difference is that I need to take the fem.petsc.LinearProblem definition out of the function, as it will be called many times and makes the execution crush.

If I tried to project from a CG of second order to say a CG of first degree, the following seems to work, as I verified on some cases

V2 = fem.VectorFunctionSpace(domain, ("CG", 2)) 
V0 = fem.VectorFunctionSpace(domain, ("CG", 1))
ut = ufl.TrialFunction(V0)
func = Function(V2)
v = ufl.TestFunction(V0)
problem_int_out = fem.petsc.LinearProblem(ufl.inner(ut,v)*dx,ufl.inner(func,v)*dx,
                                      petsc_options={"ksp_type": "preonly", "pc_type": "lu"})
def local_project_no_petsc_prob(func_arg, V, mesh):
    ut = ufl.TrialFunction(V)
    v = ufl.TestFunction(V)

   
    func.x.array[:]= func_arg.x.array
    
    u_proj = problem_int_out.solve()
    return u_proj


So I define the fem.petsc.LinearProblem linear problem outside the loop, and inside I just modify func, from said fem.petsc.LinearProblem, using the first argument of the function local_project(v, V).

In the notebook though, the function argument is not a Coefficient(FunctionSpace .... object. It is instead defined so

V0 = fem.FunctionSpace(domain, ("DG", 0))
p = fem.Constant(domain, ScalarType(1)) # SIMP penalty exponent
lagrange = fem.Constant(domain, ScalarType(1))# Lagrange multiplier for volume constraint
thetaold = Function(V0, name="Density")
thetaold.x.set(thetamoy)
thetamoy = 0.4
coeff = thetaold**p
def eps(v):
    return ufl.sym(ufl.grad(v))
def sigma(v):
    return coeff*(lamda*ufl.nabla_div(v)*ufl.Identity(2)+2*mu*eps(v))
def energy_density(u, v):
    return ufl.inner(sigma(u), eps(v))
func_arg = p*coeff*energy_density(u, u)/lagrange)**(1/(p+1)

and func_arg is of type

Power(Coefficient(FunctionSpace(Mesh(VectorElement ...

So, how can I get such an object as argument, and modify in place the function func used in defining the fem.petsc.LinearProblem? The object does not have the x.array attribute, and I am not even able to find it in the docs.
Thanks a lot for the wonderful support

I fear my question was not clear, so I have tried to reformulate it in a simpler setting, independent from any previous notebook or application.

I attach a MWE below, at least showing concretely what I am trying to do.
In essence I define a problem = fem.petsc.LinearProblem(a, L, bcs=[bc], petsc_options={"ksp_type": "preonly", "pc_type": "lu"}) and necessary arguments a, L, bcs.
Now I need to solve it in a loop where one argument form, say a is changing, but cannot call fem.petsc.LinearProblem each iteration as it will crash.
So I would like to take the fem.petsc.LinearProblem defintion out of the loop, and modify said form “in place” this I do not know how to do (it is easy to achieve for fem.constant as in the Poisson tutorial, assigning a new value each loop with fem.constant.value, without need to recompile the problem).
In the example below I define a list of forms, and would like to iterate the linear problem solution over this list.

MWE

########## DOLFINX ##############
%matplotlib notebook
from dolfinx import *
import matplotlib.pyplot as plt
import numpy as np
from petsc4py import PETSc
from petsc4py.PETSc import ScalarType
from mpi4py import MPI
from dolfinx.fem import FunctionSpace, Function
from dolfinx.fem import VectorFunctionSpace
from dolfinx.fem import petsc
from dolfinx import fem, io
from dolfinx import mesh
from dolfinx.mesh import CellType, create_rectangle

import ufl

import pyvista
pyvista.set_jupyter_backend("trame")


L = 4

domain = create_rectangle(MPI.COMM_WORLD, np.array([[0,0],[L,1]]), [30,30], cell_type=CellType.triangle)
tdim = domain.topology.dim
fdim = tdim - 1
domain.topology.create_connectivity(fdim, tdim)
boundary_facets = mesh.exterior_facet_indices(domain.topology)
# Problem parameters
thetamoy = 0.4 # target average material density
E = fem.Constant(domain, ScalarType(1))
nu = fem.Constant(domain, ScalarType(0.3))
lamda = E*nu/(1+nu)/(1-2*nu)
mu = E/(2*(1+nu))
f = fem.Constant(domain, ScalarType([0,-1])) # vertical downwards force
# Function space for density field
V0 = fem.FunctionSpace(domain, ("DG", 0))
# Function space for displacement
V2 = fem.VectorFunctionSpace(domain, ("CG", 2))
def clamped_boundary(x):
    return np.isclose(x[0], 0)

fdim = domain.topology.dim - 1
boundary_facets = mesh.locate_entities_boundary(domain, fdim, clamped_boundary)

u_D = np.array([0,0], dtype=ScalarType)

bc = fem.dirichletbc(u_D, fem.locate_dofs_topological(V2, fdim, boundary_facets), V2)



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

def right(x):
    return np.isclose(x[0], L)

fdim = domain.topology.dim -1
left_facets = mesh.locate_entities_boundary(domain, fdim, left)
right_facets = mesh.locate_entities_boundary(domain, fdim, right)
# Concatenate and sort the arrays based on facet indices. Left facets marked with 1, right facets with two
marked_facets = np.hstack([left_facets, right_facets])
marked_values = np.hstack([np.full_like(left_facets, 1), np.full_like(right_facets, 2)])
sorted_facets = np.argsort(marked_facets)
facet_tag = mesh.meshtags(domain, fdim, marked_facets[sorted_facets], marked_values[sorted_facets])




metadata = {"quadrature_degree": 4}
ds = ufl.Measure('ds', domain=domain, subdomain_data=facet_tag, metadata=metadata)
dx = ufl.Measure("dx", domain=domain, metadata=metadata)
def eps(v):
    return ufl.sym(ufl.grad(v))
def sigma(v):
    return (lamda*ufl.nabla_div(v)*ufl.Identity(2)+2*mu*eps(v))
#   return lambda_ * ufl.nabla_div(u) * ufl.Identity(len(u)) + 2*mu*epsilon(u)
def energy_density(u, v):
    return ufl.inner(sigma(u), eps(v))

# Inhomogeneous elastic variational problem
# u_ = ufl.TestFunction(V2)
# du = ufl.TrialFunction(V2)
# a = ufl.inner(sigma(u_), eps(du)) * ufl.dx
# L = ufl.dot(f, u_)*ds(2)


w = ufl.TestFunction(V2)
u = ufl.TrialFunction(V2)
a = ufl.inner(sigma(w), eps(u)) * ufl.dx
L = ufl.dot(f, w)*ds(2)

problem = fem.petsc.LinearProblem(a, L, bcs=[bc], petsc_options={"ksp_type": "preonly", "pc_type": "lu"})
uh= problem.solve()



form_list = [ k*ufl.inner(sigma(w), eps(u)) * ufl.dx for k in [1,2,3]]

for i in range(3):
    #### modify a in place, e.g. a = form_list[i]
    uh = problem.solve()

Getting into the documentation, I came up with this approach

from dolfinx.fem.forms import form as _create_form

form_list = [ k*ufl.inner(sigma(w), eps(u)) * ufl.dx for k in [1,2,3]]



for i in range(3):
    #### modify a in place, e.g. a + form_list[i]
    # The next line does not work
    # problem._a = form_list[i]
    ## Tried
    problem._a = _create_form(form_list[i])
    uh = problem.solve()
    print(uh.x.array[0])

seems create_form goes from ufl.forms to fem.forms.
Is this the way to do it? Thanks a lot

Hi Mark,

I’ll take a stab at it since nobody has replied yet. I’m a bit confused/concerned by this sentence:

Now I need to solve it in a loop where one argument form, say a is changing, but cannot call fem.petsc.LinearProblem each iteration as it will crash.

How many times are you calling LinearProblem? I ask because calling LinearProblem (or NonlinearProblem) many times is a straightforward way of solving dynamic problems with Dolfinx. I’ve run optimization problems with well over ten thousand calls to LinearProblem or NonlinearProblem without any issues (on Linux). Can you monitor why your program crashes (like keep track of memory usage, tell us what the error is, etc.)?

In any case, you can go into the code and modify the Python layer of the LinearProblem class pretty easily. You can find it in the Dolfinx documentation, here is the code corresponding to Dolfinx 0.8.

Hi there,

Thanks for your help.
I have to solve a LinearProblem hundreds of times. During my first go, I was getting a Petsc Error 55, because I was goofily re-initialising the LinearProblem every time.
So I had to take the initialisation out of the loop/function call.
I was left with the problem of how to update the rhs though (the tutorials show immediately how to mosufy a Fem.Constant)
Actually the solution i describe at the end of me second post, that is accessing the Petsc.LinearProblem and modify its attribute, as in the source code, seems to work well. I was just concerned there are better ways to do it and was curious to see how the black belts would handle this, must a be a very common situation as you point out.

It’s great that you’ve found your issue and managed to implement a work-around! It’s just that I find it odd that it’s been an issue in the first place. PETSc error code 55 seems to be unable to allocate requested memory. I don’t remember ever encountering that error code nor do I see my RAM consumption going up significantly over the course of tens of thousands of LinearProblem initialisations, so I’m somewhat surprised that it’s been an issue for you. I think the way you ended up solving it (modifying the properties of LinearProblem) is probably the easiest work-around.

That is interesting, also considering I have a monster of a laptop.
This is the full error, if this ever became interesting enough to anyone I can share the code

Error: error code 55
[0] PetscOptionsSetValue() at /Users/...
[0] PetscOptionsSetValue_Private() at /Users/...
[0] Out of memory. Allocated: 0, Used by process: 216903680
[0] Number of options 512 < max number of options 512, can not allocate enough space

I wonder if it is just a metter of increasing max number of options now you made me curious will give it a go.
Anyhow, all is well what ends well, thank you very much again

Could you please specify what variables that you need to update, i.e. what variables in your problem is re-defined such that you need to define a new form object?

In general, it is not adviced redefining forms (especially not in the case where it is just a constant varying, such as in:

which can be defined as

k_constant = dolfinx.fem.Constant(domain, 1)
form = k_constant*ufl.inner(sigma(w), eps(u)) * ufl.dx

which can now be used in a single linearproblem and updated with
k_constant.value = 2, k_constant.value=3 etc.

Thank you, indeed I was aware of the possibility to define Fem.Constant and update them trough .value, luckily it is explained in the first Poisson tutorial.
On the other hand, in this instance I need to update the rhs of a problem.
The full snippet is in my first post, to summarise I have a function

def local_project(func, V, mesh):
    ut = ufl.TrialFunction(V)
    v = ufl.TestFunction(V)
    #L = ufl.inner(func,v)*ufl.dx
    problem_int._L = _create_form( ufl.inner(func,v)*ufl.dx)
    u_proj = problem_int.solve()
return u_proj

projecting from a CG to a DG0 space, where the argument is something like

p*coeff*energy_density(u, u)/lagrange)**(1/(p+1)

where u is a a displacement from the CG space, p and lagrange are scalars, coeff equals thetaold**p, where thetaold is a field in DG0.

The solution I describe in the second post works, might not be optimaal though. The project function is called hundred of times each iteration. Thanks