Implementation of Integral Constraints in higher spatial dimensions

Hi all,
The following problem is a 2d simplification of a field theory calculation.

Consider the following problem: calculate a field u(x,y) in \Omega=[0,1]\times[0,1] that has obeys Dirichelt BCs u_{bc}=1+2x^2+3y^2 while also making sure it obeys the integral constraint \int\limits_0^{1/2} {dy} u\left( {\frac{1}{2},y} \right) = 1.

The constraint can be written as (\delta is the delta function and \theta is the Heaviside step function):
\int\limits_0^{1/2} {dy} u\left( {\frac{1}{2},y} \right) = \int {d\Omega } \delta \left( {x - \frac{1}{2}} \right)\theta \left( {\frac{1}{2} - y} \right)u(x,y) \equiv \int {d\Omega } g(x,y)u(x,y) = 1

The action is (\lambda is a Lagrange Multiplier):
S = \int {d\Omega } {\left| {\nabla u(x,y)} \right|^2} - \int {d\Omega } f(x,y)u(x,y) - \lambda \left[ {\int\limits_0^{1/2} {dy} u\left( {\frac{1}{2},y} \right) - 1} \right] = \int {d\Omega } \left[ {{{\left| {\nabla u} \right|}^2} - fu - \lambda gu} \right]
Minimization towards the LM gives back the integral constraint while minimizing towards the field gives the PDE:
- {\nabla ^2}u(x,y) = f(x,y) + \lambda g(x,y).
To solve the problem, I suggest the following procedure:

  1. Find the variational form of the PDE. Say that the trial function is \phi_i while the test function is \sum\limits_j {{u_j}{\varphi _j}} where \phi are proper elements for this problem. The variational form, thus, is:
    \sum\limits_j {{A_{ij}}{u_j}} = {F_i} + \lambda {G_i}
    where {A_{ij}} = \int {d\Omega } \nabla {\varphi _i} \cdot \nabla {\varphi _j},~~~{F_i}=\int {d\Omega } f{\varphi _i},~~~{G_i}=\int {d\Omega } g{\varphi _i}
  2. Do the same in the integral constraint: \sum\limits_j {{G_j}{u_j}} \equiv G \cdot u = 1
  3. Write a matrix system as follows:\left[ {\begin{array}{*{20}{c}} A&G\\ {{G^ \bot }}&0 \end{array}} \right]\left[ {\begin{array}{*{20}{c}} u\\ { - \lambda } \end{array}} \right] = \left[ {\begin{array}{*{20}{c}} F\\ { - 1} \end{array}} \right]
  4. Make the usual assumptions: \delta (x - a) \to \frac{1}{{\sigma \sqrt {2\pi } }}\exp \left[ { - \frac{{{{\left( {x - a} \right)}^2}}}{{2{\sigma ^2}}}} \right],\theta (x - a) \to \frac{1}{{1 + {e^{ - \frac{{x - a}}{\varepsilon }}}}}
  5. Solve the problem in FEniCSx.

However, I cannot implement the problem correctly in FEniCSx. A code that presents the above idea but doesn’t work and the error will be attached in the end.

Therefore I have the following questions for discussion. Firstly, are the above correct? How can I do a proper implementation in FEniCSx? Secondly, can you suggest another viable option? I see the discussions in other topics about null spaces but I not familiar with them and my problem is not singular.

Finally, can you think of a “better” way to simulate the delta and step functions? Numerical tests in simple problems show high dependance of their parameters in the computational grid.

Thanks!!

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

# Create a mesh for the unit square domain
domain = dolfinx.mesh.create_unit_square(MPI.COMM_WORLD, 50, 50, dolfinx.mesh.CellType.quadrilateral)

V = dolfinx.fem.FunctionSpace(domain, ("CG", 3))
uD = dolfinx.fem.Function(V)
uD.interpolate(lambda x: 1 + x[0]**2 + 2 * x[1]**2)
spatial_dimension = domain.topology.dim
domain.topology.create_connectivity(domain.topology.dim-1, domain.topology.dim)
boundary_facets = dolfinx.mesh.exterior_facet_indices(domain.topology)
boundary_dofs = dolfinx.fem.locate_dofs_topological(V, domain.topology.dim-1, boundary_facets)
dof_coordinates = V.tabulate_dof_coordinates()[boundary_dofs]
bc = dolfinx.fem.dirichletbc(uD, boundary_dofs)

# Define test and trial functions
u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)

# Define the source term f(x, y)
f = dolfinx.fem.Constant(domain, petsc4py.PETSc.ScalarType(-6)) # Define your source term here, e.g., ufl.Constant(0)

x = ufl.SpatialCoordinate(domain)
ic_term = dolfinx.fem.Constant(domain, petsc4py.PETSc.ScalarType(0))
sigma_d=10**(-3)
sigma_s=10**(-5)
mean_d=0.5
mean_s=0.5
spatial_dimension = 2
dx_temp = ufl.exp(-((x[0] - mean_d)**2) / (2 * sigma_d**2)) / (sigma_d * ufl.sqrt(2 * ufl.pi)) * ((1 / (1 + ufl.exp(-(mean_s - x[1]) / sigma_s)))-(1 / (1 + ufl.exp(-(mean_s - x[1]) / sigma_s))))

# Define and solve the variational problem without constraint
a = ufl.inner(ufl.grad(u), ufl.grad(v)) * ufl.dx
L = f * v * ufl.dx
problem_unconstrained = dolfinx.fem.petsc.LinearProblem(a, L, bcs=[bc])
u_unconstrained = problem_unconstrained.solve()
u_unconstrained_array=np.array(u_unconstrained.x.array)
print(u_unconstrained_array)

# Define the integral constraint as a linear form
g = dx_temp 
constraint_form = ufl.inner(g, v) * ufl.dx  # Define the domain of the line integral
constraint_form_transpose = ufl.inner(v, g) * ufl.dx
# Stiffness Matrix
a_ic = dolfinx.fem.form([[a, constraint_form], [constraint_form_transpose, None]])
# Load Vector
L_ic = dolfinx.fem.form([[L], [1]])

problem_constrained = dolfinx.fem.petsc.LinearProblem(a_ic, L_ic, bcs=[bc])
u_constrained = problem_constrained.solve()

u_constrained_array=np.array(u_constrained.x.array)
Lagrange_multiplier = u_constrained_array[-1]
print(Lagrange_multiplier)

Error:

  File "/Tests.py", line 54, in <module>
    problem_constrained = dolfinx.fem.petsc.LinearProblem(a_ic, L_ic, bcs=[bc])
  File "/usr/lib/petsc/lib/python3/dist-packages/dolfinx/fem/petsc.py", line 589, in __init__
    self._A = create_matrix(self._a)
  File "/usr/lib/petsc/lib/python3/dist-packages/dolfinx/fem/petsc.py", line 139, in create_matrix
    return _cpp.fem.petsc.create_matrix(a._cpp_object)
AttributeError: 'list' object has no attribute '_cpp_object'
Exception ignored in: <function LinearProblem.__del__ at 0x7f4d7e899900>
Traceback (most recent call last):
  File "/usr/lib/petsc/lib/python3/dist-packages/dolfinx/fem/petsc.py", line 627, in __del__
    self._solver.destroy()
AttributeError: 'LinearProblem' object has no attribute '_solver'

Not sure if I really understood your problem, but to me it seems like an ill posed problem.

You are trying to solve a Poisson equation with Dirichlet boundary conditions. The solution is unique. Its integral in the midline x = 0.5 is either one or (most probably) isn’t one. If it is one, then you are lucky and your constraint is also verified. If it is not one, then there is no way you’d be able to find another solution that satisfies the constraint, because that would go against its uniqueness.

On the programming error you get, please make a search in the forum, it was asked several times before. Off the top of my head, if I remember right dolfinx.fem.petsc.LinearProblem expects the forms to be ufl forms.

I perhaps did not explain the problem that well. Imposing the constraint changes the PDE and therefore changes the solution. The new PDE, that includes the Lagrange Multiplier, has indeed a unique solution that also respects the constraint. There are analytically solvable 1d problems that better encapsulate this.

As for the numerical part you are right, however I am not sure how to make my system as a ufl form without defining trial and test functions for the Lagrange Multiplier.

As for the numerical part you are right, however I am not sure how to make my system as a ufl form without defining trial and test functions for the Lagrange Multiplier.

You can surely call petsc4py yourself. For instance, have a look at run_block in tutorial_block_poisson. The only thing you would need to change is the part below # Split the block solution in components, where (unless you want to install my library) you should be using something similar to dolfinx/python/demo/demo_navier-stokes.py at 920e91f2a253e43c2e5bcb229a4f9a3274504c70 · FEniCS/dolfinx · GitHub

I did not realize that you are the creator of multiphenicsx! To be honest I was aware of this and in the previous days I have tried something similar (following your tutorial and how one could solve Navier-Stokes) but I was stricken with an error in AA = dolfinx.fem.petsc.assemble_matrix_block(aa_cpp, bcs) that seems to be about the dimensions but I don’t clearly understand. Error and minimal code follow
Error:

[0]PETSC ERROR: ------------------------------------------------------------------------
[0]PETSC ERROR: Caught signal number 11 SEGV: Segmentation Violation, probably memory access out of range
[0]PETSC ERROR: Try option -start_in_debugger or -on_error_attach_debugger
[0]PETSC ERROR: or see https://petsc.org/release/faq/#valgrind
[0]PETSC ERROR: or try http://valgrind.org on GNU/linux and Apple Mac OS X to find memory corruption errors
[0]PETSC ERROR: configure using --with-debugging=yes, recompile, link, and run
[0]PETSC ERROR: to get more information on the crash.
[0]PETSC ERROR: Run with -malloc_debug to check if memory corruption is causing the crash.
--------------------------------------------------------------------------
MPI_ABORT was invoked on rank 0 in communicator MPI_COMM_WORLD
with errorcode 59.

NOTE: invoking MPI_ABORT causes Open MPI to kill all MPI processes.
You may or may not see output from other processes, depending on
exactly when Open MPI kills them.

MWE:

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

# Create a mesh for the unit square domain
domain = dolfinx.mesh.create_unit_square(MPI.COMM_SELF, 10, 10, dolfinx.mesh.CellType.quadrilateral)
V = dolfinx.fem.FunctionSpace(domain, ("CG", 3))
uD = dolfinx.fem.Function(V)
uD.interpolate(lambda x: 1 + x[0]**2 + 2 * x[1]**2)
spatial_dimension = domain.topology.dim
domain.topology.create_connectivity(domain.topology.dim-1, domain.topology.dim)
boundary_facets = dolfinx.mesh.exterior_facet_indices(domain.topology)
boundary_dofs = dolfinx.fem.locate_dofs_topological(V, domain.topology.dim-1, boundary_facets)
dof_coordinates = V.tabulate_dof_coordinates()[boundary_dofs]
bc = dolfinx.fem.dirichletbc(uD, boundary_dofs)

# Define test and trial functions
u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)

# Define the source term f(x, y)
f = dolfinx.fem.Constant(domain, petsc4py.PETSc.ScalarType(-6)) # Define your source term here, e.g., ufl.Constant(0)
x = ufl.SpatialCoordinate(domain)
sigma_d=10**(-3)
sigma_s=10**(-5)
mean_d=0.5
mean_s=0.5
spatial_dimension = 2
dx_temp = ufl.exp(-((x[0] - mean_d)**2) / (2 * sigma_d**2)) / (sigma_d * ufl.sqrt(2 * ufl.pi)) * ((1 / (1 + ufl.exp(-(mean_s - x[1]) / sigma_s)))-(1 / (1 + ufl.exp(-(mean_s - x[1]) / sigma_s))))

# Define and solve the variational problem without constraint
a = ufl.inner(ufl.grad(u), ufl.grad(v)) * ufl.dx
L = ufl.inner(f , v) * ufl.dx
problem_unconstrained = dolfinx.fem.petsc.LinearProblem(a, L, bcs=[bc])
u_unconstrained = problem_unconstrained.solve()
u_unconstrained_array=np.array(u_unconstrained.x.array)

# Define the integral constraint as a linear form
g = dx_temp 
# Define problem block forms
aa = [[ufl.inner(ufl.grad(u), ufl.grad(v)) * ufl.dx, ufl.inner(g , v) * ufl.dx],[ufl.inner(v , g) * ufl.dx, None]]
ff = [f * v * ufl.dx, petsc4py.PETSc.ScalarType(1)]
aa_cpp = dolfinx.fem.form(aa)
ff_cpp = dolfinx.fem.form(ff)
bcs = [bc, None]
# Assemble the block linear system
AA = dolfinx.fem.petsc.assemble_matrix_block(aa_cpp, bcs)
AA.assemble()
FF = dolfinx.fem.petsc.assemble_vector_block(ff_cpp, aa_cpp, bcs)

That’s certainly wrong:

  • you can’t have the same test function in two rows of the matrix.
  • the term ufl.inner(g , v) * ufl.dx should be a bilinear form, but seems to be a linear form because g is known.

Even if that was correct, it would probably not be what your are looking for. From what I understood in your post, you expect \lambda to be a single scalar value constant throughout the domain (what used to be called "Real" element in legacy FEniCS), but that is not available in current dolfinx. multiphenicsx will not help either: multiphenicsx is able to restrict unknowns to part of the domain or part of the boundary, but cannot define an equivalent of the former "Real" element.

Please try to rephrase again your problem, in a way that does show us that my claim “your problem is ill-posed” is false.

Thanks for the feedback so far. Let me try to show why the problem, to my knowledge, is not ill posed. A PDE plus Dirichlet BCs has a unique solution, say u(x,y).

However, adding any kind of constraint changes the PDE by adding source terms. The new solution that respects both the BCs and the constraint is again unique, but it is different than u(x,y). Something similar happens if you want to calculate the magnetic field in a domain, while respecting also that \nabla \cdot {\bf{B}} = 0.

A 1D toy example of what I have in the original post is to calculate an 1D field in Unit line that has the following properties: u(0)=0.5, u(1)=1.5, \int\limits_0^{0.5} {dx} u(x) = 2. Using the logic in the original post one can analytically find the solution u(x) =-\frac{312}{10}{x^2} + \frac{122}{5}x + {\frac{1}{2}}, ~~~ x \in [0,0.5] and u(x) =-\frac{34}{5}x + {\frac{83}{10}}, ~~~ x \in [0.5,1] .

You are correct, I am trying to do what it was done in legacy FEniCS with the “Real” element. I haven’t tried it myself but I have seen the literature and wanted FEniCSx due to gmsh. In my example I wanted to create a general matrix that its inversion gave both the solution and the Lagrange Multiplier

OK, I may try to reinterpret your problem has an optimal control / PDE constrained optimization problem. Within that setting, lambda would be a control, and your cost function would be to have the integral over a certain part of the domain as close as a desired value as possible. What you tried to write were the optimality conditions of the problem.

In the tutorial 06 folder of multiphenicsx there are several examples, for instance tutorial_1a_poisson, but you’d probably need to have a look at a book on that topic to understand the formulation and the optimality conditions, as I haven’t really discussed that in any detail. Unfortunately, none of the examples will bring you closer to the case you want because lambda defined with the former Real element is not currently available in dolfinx. There may be alternative solution methods that could be implemented, but I am not familiar with them.

At the very least hopefully this discussion has given you two keywords (PDE constrained optimization, or optimal control for PDEs) that can be helpful for carrying out a literature review.

Thanks for bringing my attention to this. I was not familiar with optimal control, although it may not be that applicable in this calculation it will be useful in the future.

Up to this point I have treated my problem as a conventional constrained optimization problem. This means that, having a fenicsx code, that if I give a value of \lambda calculates the field u, proper numerical minimization of the constrained condition towards \lambda calculates both the LM and the field.

However, this may be impractical if you have more than one constraints and the presence of the delta and step functions complicates things more. In my initial post I described a, in my opinion, more straightforward way to introduce a constraint and it was trivial to implement it in 1d without the need of fenicsx.

In any case thanks for the discussion!