Tangential gradient issue

Hi everybody!

I am working on an ESFEM problem and have the following discretization:

I am encountering an issue specifically with $\boldsymbol{T}_h^m$​, which is defined as:

image

This is my MWE

import os
import time
import numpy as np
from mpi4py import MPI  # type: ignore
from petsc4py import PETSc  # type: ignore

# Matplotlib setup
import matplotlib as mpl  # type: ignore
mpl.use('Agg')
import matplotlib.pyplot as plt  # type: ignore

# FEniCSx and UFL imports
import dolfinx  # type: ignore
import basix.ufl  # type: ignore
import ufl  # type: ignore
from basix.ufl import element, mixed_element  # type: ignore
from dolfinx import mesh, log, default_real_type  # type: ignore
from dolfinx.fem import ( # type: ignore
    Function, functionspace, Expression, form, Constant  # type: ignore
)
from dolfinx.fem.petsc import ( # type: ignore
    NonlinearProblem, assemble_matrix, assemble_vector, create_vector  # type: ignore
)
from dolfinx.nls.petsc import NewtonSolver  # type: ignore
from dolfinx.io import XDMFFile, gmshio  # type: ignore
from ufl import dx, grad, inner, div, dot  # type: ignore

try:
    import gmsh  # type: ignore
except ImportError:
    import sys
    print("This program requires gmsh to be installed")
    sys.exit(0)

start = time.perf_counter()

gmsh.initialize()

h = 0.5e-2
R = 0.504
dt = 1e-3

gmsh_model_rank = 0
mesh_comm = MPI.COMM_WORLD
rank = MPI.COMM_WORLD.rank

if mesh_comm.rank == gmsh_model_rank:

    membrane = gmsh.model.occ.addDisk(0, 0, 0, R, R) # x, y, z, x-radius, y-radius
    gmsh.model.occ.synchronize()

    # Make membrane a physical surface for GMSH to recognise when generating the mesh
    gdim = 1 # 2D Geometric Dimension of the Mesh
    gmsh.model.addPhysicalGroup(gdim, [membrane], 0) # Dimension, Entity tag, Physical tag

    # Generate 2D Mesh with uniform mesh size
    gmsh.option.setNumber("Mesh.CharacteristicLengthMin", h)
    gmsh.option.setNumber("Mesh.CharacteristicLengthMax", h)
    gmsh.model.mesh.generate(gdim)
    gmsh.model.mesh.setOrder(2)

mesh, cell_markers, _ = gmshio.model_to_mesh(gmsh.model, mesh_comm, gmsh_model_rank, gdim=2)
mesh.name = "initial_mesh"

with XDMFFile(MPI.COMM_WORLD, "circle.xdmf", "w") as xdmf:
    xdmf.write_mesh(mesh)
    xdmf.write_meshtags(cell_markers, mesh.geometry)

# Finalize GMSH

mesh.topology.create_connectivity(mesh.topology.dim-1, mesh.topology.dim)

gmsh.finalize()

P1 = element("Lagrange", mesh.basix_cell(), 2, gdim=2)
P2 = element("Lagrange", mesh.basix_cell(), 2, shape=(mesh.geometry.dim,), gdim=2)

ME = functionspace(mesh, mixed_element([P1, P1], gdim=2))
MEx = functionspace(mesh, mixed_element([P2, P1], gdim=2))

V = functionspace(mesh, P2)
Q = functionspace(mesh, P1)

(Xh, Hh) = ufl.TrialFunctions(MEx)
(Xi, phi) = ufl.TestFunctions(MEx)

coordinates = mesh.geometry.x[:, :2]
   
q, v = ufl.TestFunctions(ME)

# +
u = Function(ME)  # current solution
u0 = Function(ME)  # solution from previous converged step

Xh0 = Function(V) 
Xn = Function(V) 
Hh0 = Function(Q) 
Hn = Function(Q) 

# Split mixed functions
u1, u2 = ufl.split(u)
u10, u20 = ufl.split(u0)

k1 = 0.1
k2 = 2.0

ui = k1+k2
vi = k2/(ui)**2

# Interpolate initial condition

u.sub(0).x.array[:] = ui
u.sub(1).interpolate(lambda x: vi + np.maximum(1e-3 * x[0], 0)) 

# Finalize initialization of solution variables
u.x.scatter_forward()

# Compute oriented normal vectors
n = ufl.CellNormal(mesh)
xc = ufl.SpatialCoordinate(mesh)
r = xc / ufl.sqrt(ufl.dot(xc, xc))  # Radial unit vector
sign = ufl.sign(ufl.dot(n, r))  # Adjust sign for orientation consistency
n_oriented = sign * n  # Corrected normal vector
t_oriented = ufl.as_vector((-n_oriented[1], n_oriented[0]))

# Define coordinate expression for interpolation
def x_exp(x):
    """Expression for coordinate interpolation."""
    return np.vstack((x[0], x[1]))

# Interpolate mean curvature field
H_expr = Expression(div(sign * n), Q.element.interpolation_points())
Hh0.interpolate(H_expr)

# Define tangential projection operator
def Tan(X,nu):
    """Compute tangential projection operator."""
    return (X) / dt - nu*inner((X) / dt, nu)

# Define geometric function space and functions
V_geom = functionspace(mesh, P2)
X_geom = Function(V_geom)

# Initialize displacement field
Xh0.interpolate(x_exp)
X_geom.interpolate(Xh0)

# Define simulation parameters
kp = ufl.as_vector([-2,1])  # Reaction coefficient
k = dt  # Time step size
d = 10  # Diffusion coefficient
gamma = 10  # Reaction rate constant
a, b = k1, k2  # Model parameters
tol = 1e-4  # Tolerance for solver

d1, d2 = 0.5, 50  # Diffusion coefficients

# Stiffness coefficients
ks, kb = 2, 2

# Define weak formulation of the system
F = (
    ((u1 - u10) / k) * q * dx
    + d1 * inner(grad(u1), grad(q)) * dx
    - div(u1*Tan(X_geom-Xh0, n_oriented)) * q * dx
    - (gamma * (u1**2 * u2 - u1 + a)) * q * dx
    + ((u2 - u20) / k) * v * dx
    - div(u2*Tan(X_geom-Xh0, n_oriented)) * v * dx
    + d2 * inner(grad(u2), grad(v)) * dx
    - (gamma * (-u1**2 * u2 + b)) * v * dx
)

problem = NonlinearProblem(F, u)
solver = NewtonSolver(MPI.COMM_WORLD, problem)
solver.convergence_criterion = "incremental"
solver.rtol = np.sqrt(np.finfo(default_real_type).eps) * 1e-2

The specific issue arises on the line: problem = NonlinearProblem(F, u)

The rest of the implementation is working properly, but the problem appears when including the term div(u1*Tan(X_geom-Xh0, n_oriented)) * q * dx

I would really appreciate any help

Simplifying this code to be a pure ufl-example makes it way easier to look at the issue:

import ufl


from ufl.finiteelement import FiniteElement,MixedElement
from ufl.pullback import identity_pullback
from ufl.sobolevspace import H1

from ufl.algorithms import compute_form_data


gdim = 2
geom_degree = 2
mesh = ufl.Mesh(FiniteElement("Lagrange", ufl.interval, geom_degree, (gdim,), identity_pullback, H1))


P1 = FiniteElement("Lagrange", ufl.interval, 2, (), identity_pullback, H1)
P2 = FiniteElement("Lagrange", ufl.interval, 2, (gdim,), identity_pullback, H1)

ME = ufl.FunctionSpace(mesh, MixedElement([P1, P1]))
MEx = ufl.FunctionSpace(mesh, MixedElement([P2, P1]))

V = ufl.FunctionSpace(mesh, P2)
Q = ufl.FunctionSpace(mesh, P1)

(Xh, Hh) = ufl.TrialFunctions(MEx)
(Xi, phi) = ufl.TestFunctions(MEx)

   
q, v = ufl.TestFunctions(ME)

# +
u = ufl.Coefficient(ME)  # current solution
u0 = ufl.Coefficient(ME)  # solution from previous converged step

Xh0 = ufl.Coefficient(V) 
Xn = ufl.Coefficient(V) 
Hh0 = ufl.Coefficient(Q) 
Hn = ufl.Coefficient(Q) 

# Split mixed functions
u1, u2 = ufl.split(u)
u10, u20 = ufl.split(u0)

k1 = 0.1
k2 = 2.0
dt = 1e-3
ui = k1+k2
vi = k2/(ui)**2



# Compute oriented normal vectors
n = ufl.CellNormal(mesh)
xc = ufl.SpatialCoordinate(mesh)
r = xc / ufl.sqrt(ufl.dot(xc, xc))  # Radial unit vector
sign = ufl.sign(ufl.dot(n, r))  # Adjust sign for orientation consistency
n_oriented = sign * n  # Corrected normal vector
t_oriented = ufl.as_vector((-n_oriented[1], n_oriented[0]))



# Define tangential projection operator
def Tan(X,nu):
    """Compute tangential projection operator."""
    return (X) / dt - nu*ufl.inner((X) / dt, nu)

# Define geometric function space and functions
V_geom = ufl.FunctionSpace(mesh, P2)
X_geom = ufl.Coefficient(V_geom)

# Initialize displacement field

# Define simulation parameters
kp = ufl.as_vector([-2,1])  # Reaction coefficient
k = dt  # Time step size
d = 10  # Diffusion coefficient
gamma = 10  # Reaction rate constant
a, b = k1, k2  # Model parameters
tol = 1e-4  # Tolerance for solver

d1, d2 = 0.5, 50  # Diffusion coefficients

# Stiffness coefficients
ks, kb = 2, 2

# Define weak formulation of the system
F = - ufl.div(u1*Tan(X_geom-Xh0, n_oriented)) * q * ufl.dx

J = ufl.derivative(F,u)

fd = compute_form_data(
        J,
        do_apply_function_pullbacks=True,
        do_apply_default_restrictions=True,
        do_apply_geometry_lowering=True,
        do_apply_restrictions=True,
        complex_mode=False,
    )

This produces the error:

Traceback (most recent call last):
  File "/root/shared/debug/mwe_ufl.py", line 91, in <module>
    fd = compute_form_data(
         ^^^^^^^^^^^^^^^^^^
  File "/root/shared/ufl/ufl/algorithms/compute_form_data.py", line 288, in compute_form_data
    form = preprocess_form(form, complex_mode)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/root/shared/ufl/ufl/algorithms/compute_form_data.py", line 244, in preprocess_form
    form = apply_derivatives(form)
           ^^^^^^^^^^^^^^^^^^^^^^^
  File "/root/shared/ufl/ufl/algorithms/apply_derivatives.py", line 1640, in apply_derivatives
    dexpression_dvar = map_integrand_dags(rules, expression)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/root/shared/ufl/ufl/algorithms/map_integrands.py", line 86, in map_integrand_dags
    return map_integrands(
           ^^^^^^^^^^^^^^^
  File "/root/shared/ufl/ufl/algorithms/map_integrands.py", line 30, in map_integrands
    map_integrands(function, itg, only_integral_type) for itg in form.integrals()
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/root/shared/ufl/ufl/algorithms/map_integrands.py", line 39, in map_integrands
    return itg.reconstruct(function(itg.integrand()))
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/root/shared/ufl/ufl/algorithms/map_integrands.py", line 87, in <lambda>
    lambda expr: map_expr_dag(function, expr, compress), form, only_integral_type
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/root/shared/ufl/ufl/corealg/map_dag.py", line 35, in map_expr_dag
    (result,) = map_expr_dags(
                ^^^^^^^^^^^^^^
  File "/root/shared/ufl/ufl/corealg/map_dag.py", line 103, in map_expr_dags
    r = handlers[v._ufl_typecode_](v, *(vcache[u] for u in v.ufl_operands))
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/root/shared/ufl/ufl/algorithms/apply_derivatives.py", line 1485, in coefficient_derivative
    mapped_expr = map_expr_dag(rules, f, vcache=self.vcaches[key], rcache=self.rcaches[key])
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/root/shared/ufl/ufl/corealg/map_dag.py", line 35, in map_expr_dag
    (result,) = map_expr_dags(
                ^^^^^^^^^^^^^^
  File "/root/shared/ufl/ufl/corealg/map_dag.py", line 101, in map_expr_dags
    r = handlers[v._ufl_typecode_](v)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/root/shared/ufl/ufl/algorithms/apply_derivatives.py", line 1138, in reference_grad
    raise NotImplementedError(
NotImplementedError: Currently no support for ReferenceGrad in CoefficientDerivative.

which then by a hunch can be reduced to the following minimal example:

import ufl


from ufl.finiteelement import FiniteElement
from ufl.pullback import identity_pullback
from ufl.sobolevspace import H1

from ufl.algorithms import compute_form_data


gdim = 2
geom_degree = 2
mesh = ufl.Mesh(FiniteElement("Lagrange", ufl.interval, geom_degree, (gdim,), identity_pullback, H1))


P2 = FiniteElement("Lagrange", ufl.interval, 2, (), identity_pullback, H1)
V = ufl.FunctionSpace(mesh, P2)
u = ufl.Coefficient(V)
n = ufl.CellNormal(mesh)
F = u * ufl.div(n)* ufl.dx

J = ufl.derivative(F, u)
J_ref = ufl.TrialFunction(V)*ufl.div(n)*ufl.dx


fd = compute_form_data(
        J_ref,
        do_apply_function_pullbacks=True,
        do_apply_default_restrictions=True,
        do_apply_geometry_lowering=True,
        do_apply_restrictions=True,
        complex_mode=False,
    )


fd = compute_form_data(
        J,
        do_apply_function_pullbacks=True,
        do_apply_default_restrictions=True,
        do_apply_geometry_lowering=True,
        do_apply_restrictions=True,
        complex_mode=False,
    )

which indicates that the pre-processing of the UFL form isn’t quite right when using ufl.derivative (which is used to compute the jacobian in your case).

This can be made into a non-manifold issue by some more modification:

import ufl


from ufl.finiteelement import FiniteElement
from ufl.pullback import identity_pullback
from ufl.sobolevspace import H1

from ufl.algorithms import compute_form_data

gdim = 2
geom_degree = 2
mesh = ufl.Mesh(FiniteElement("Lagrange", ufl.triangle, geom_degree, (gdim,), identity_pullback, H1))


P2 = FiniteElement("Lagrange", ufl.triangle, 1, (), identity_pullback, H1)
V = ufl.FunctionSpace(mesh, P2)
u = ufl.Coefficient(V)


ds = ufl.Measure("ds", domain=mesh)

n = ufl.FacetNormal(mesh)[0]
F = u * n.dx(0) *ds
J = ufl.derivative(F, u)
J_ref = ufl.TrialFunction(V)*n.dx(0)*ds



fd = compute_form_data(
        J_ref,
        do_apply_function_pullbacks=True,
        do_apply_default_restrictions=True,
        do_apply_geometry_lowering=True,
        do_apply_restrictions=True,
        complex_mode=False,
    )


fd = compute_form_data(
        J,
        do_apply_function_pullbacks=False,
        do_apply_default_restrictions=False,
        do_apply_geometry_lowering=False,
        do_apply_restrictions=False,
        complex_mode=False,
    )

Issue made at:

2 Likes

Potential fix at:

1 Like

Thank you Dokken. I’m not quite sure how to resolve the issue in my code. I’ve tried a few things on my own, but I’m still stuck.

Would you be able to give me some guidance or suggest how I might approach fixing it?

Thank you in advance for your time.