Integrate a flux over an internal curve

I have a 2D geometry with an annulus shape and 2 internal curves, one at \theta=0, the other at \theta=\pi/2 (polar coordinates). I solve 2 coupled PDEs for the variables T and V (approximated as continuous Lagrange polynomials of degree 4). Then, after solving the FEM problem, I form \vec J in terms of T and V as \vec J = -\sigma \nabla V -\sigma S \nabla T where \sigma is a scalar that depends on the region:

R = functionspace(the_mesh.mesh, ("DG", 0))
sigma = Function(R)
sigma.x.array[three_quarters_cells] = np.full_like(three_quarters_cells, 1/(1e0), dtype=default_scalar_type)
sigma.x.array[quarter_cells] = np.full_like(quarter_cells, 1/1, dtype=default_scalar_type)

And S is a tensor (a diagonal 2x2 matrix) that also depends on the region:


        s_xx, s_yy = self.input_parameters.seebeck_components

        # Dokken's solution below
        tensor_el = element(family="DG", cell=str(the_mesh.mesh.ufl_cell()), degree=0, shape=(2, 2))

        T = functionspace(the_mesh.mesh, tensor_el)
        Seebeck_tensor = Function(T)

        def seebeck_three_quarters(x):
            tensor = np.array([[0, 0], [0, 0]])
            values = np.repeat(tensor, x.shape[1])
            #print(values.reshape(tensor.shape[0]*tensor.shape[1], x.shape[1]))
            return values.reshape(tensor.shape[0]*tensor.shape[1], x.shape[1])


        def seebeck_quarter(x):
            tensor = np.array([[s_xx, 0], [0, s_yy]])
            values = np.repeat(tensor, x.shape[1])
            #print(values.reshape(tensor.shape[0]*tensor.shape[1], x.shape[1]))
            return values.reshape(tensor.shape[0]*tensor.shape[1], x.shape[1])

        Seebeck_tensor.interpolate(seebeck_three_quarters, cells0=self.the_mesh.cell_markers.find(20))
        Seebeck_tensor.interpolate(seebeck_quarter, cells0=self.the_mesh.cell_markers.find(21))
        self.Seebeck_tensor = Seebeck_tensor

The usual


I_theta0 = assemble_scalar(form(dot(J_vector, n) * dS(22)))

print(f'I_theta0: {I_theta0}')

yields an error about restrictions whose traceback finishes by


  File "/home/isaac/.conda/envs/fenicsx-env/lib/python3.13/site-packages/ufl/algorithms/apply_restrictions.py", line 61, in _require_restriction

    raise ValueError(f"Discontinuous type {o._ufl_class_.__name__} must be restricted.")

ValueError: Discontinuous type Coefficient must be restricted.

I tried several things, like using (‘+’), but nothing worked.

For example

    J_integrand_on_facet = (-sigma('+') * grad(volt)('+') 
                                - sigma('+') * self.thermoelec_sim.Seebeck_tensor('+') * grad(temp)('+'))
        
        # Or, relying on UFL to correctly handle traces of gradients of CG functions:
        # J_integrand_on_facet = (-sigma('+') * grad(volt) 
        #                         - sigma('+') * self.thermoelec_sim.Seebeck_tensor('+') * grad(temp))

        I_theta0 = assemble_scalar(form(dot(J_integrand_on_facet, n) * dS(22)))
        print(f'I_theta0: {I_theta0}')

yields

Traceback (most recent call last):

  File "/home/isaac/Documents/fenicsx/Bridgman/2d_bridgman_textbook_example/debug_quarter_annulus_renormalize_PF/full_annulus_Lukosz_v0dot9.py", line 338, in <module>

    post_proc.post_proc()

    ~~~~~~~~~~~~~~~~~~~^^

  File "/home/isaac/Documents/fenicsx/Bridgman/2d_bridgman_textbook_example/debug_quarter_annulus_renormalize_PF/full_annulus_Lukosz_v0dot9.py", line 322, in post_proc

    I_theta0 = assemble_scalar(form(dot(J_integrand_on_facet, n) * dS(22)))

                               ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

  File "/home/isaac/.conda/envs/fenicsx-env/lib/python3.13/site-packages/dolfinx/fem/forms.py", line 337, in form

    return _create_form(form)

  File "/home/isaac/.conda/envs/fenicsx-env/lib/python3.13/site-packages/dolfinx/fem/forms.py", line 331, in _create_form

    return _form(form)

  File "/home/isaac/.conda/envs/fenicsx-env/lib/python3.13/site-packages/dolfinx/fem/forms.py", line 254, in _form

    ufcx_form, module, code = jit.ffcx_jit(

                              ~~~~~~~~~~~~^

        mesh.comm, form, form_compiler_options=form_compiler_options, jit_options=jit_options

        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    )

    ^

  File "/home/isaac/.conda/envs/fenicsx-env/lib/python3.13/site-packages/dolfinx/jit.py", line 62, in mpi_jit

    return local_jit(*args, **kwargs)

  File "/home/isaac/.conda/envs/fenicsx-env/lib/python3.13/site-packages/dolfinx/jit.py", line 212, in ffcx_jit

    r = ffcx.codegeneration.jit.compile_forms([ufl_object], options=p_ffcx, **p_jit)

  File "/home/isaac/.conda/envs/fenicsx-env/lib/python3.13/site-packages/ffcx/codegeneration/jit.py", line 225, in compile_forms

    raise e

  File "/home/isaac/.conda/envs/fenicsx-env/lib/python3.13/site-packages/ffcx/codegeneration/jit.py", line 205, in compile_forms

    impl = _compile_objects(

        decl,

    ...<9 lines>...

        visualise=visualise,

    )

  File "/home/isaac/.conda/envs/fenicsx-env/lib/python3.13/site-packages/ffcx/codegeneration/jit.py", line 330, in _compile_objects

    _, code_body = ffcx.compiler.compile_ufl_objects(

                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^

        ufl_objects, prefix=module_name, options=options, visualise=visualise

        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    )

    ^

  File "/home/isaac/.conda/envs/fenicsx-env/lib/python3.13/site-packages/ffcx/compiler.py", line 108, in compile_ufl_objects

    analysis = analyze_ufl_objects(ufl_objects, options["scalar_type"])  # type: ignore

  File "/home/isaac/.conda/envs/fenicsx-env/lib/python3.13/site-packages/ffcx/analysis.py", line 94, in analyze_ufl_objects

    form_data = tuple(_analyze_form(form, scalar_type) for form in forms)

  File "/home/isaac/.conda/envs/fenicsx-env/lib/python3.13/site-packages/ffcx/analysis.py", line 94, in <genexpr>

    form_data = tuple(_analyze_form(form, scalar_type) for form in forms)

                      ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^

  File "/home/isaac/.conda/envs/fenicsx-env/lib/python3.13/site-packages/ffcx/analysis.py", line 180, in _analyze_form

    form_data = ufl.algorithms.compute_form_data(

        form,

    ...<6 lines>...

        complex_mode=complex_mode,

    )

  File "/home/isaac/.conda/envs/fenicsx-env/lib/python3.13/site-packages/ufl/algorithms/compute_form_data.py", line 337, in compute_form_data

    form = apply_restrictions(form)

  File "/home/isaac/.conda/envs/fenicsx-env/lib/python3.13/site-packages/ufl/algorithms/apply_restrictions.py", line 183, in apply_restrictions

    return map_integrand_dags(rules, expression, only_integral_type=integral_types)

  File "/home/isaac/.conda/envs/fenicsx-env/lib/python3.13/site-packages/ufl/algorithms/map_integrands.py", line 86, in map_integrand_dags

    return map_integrands(

        lambda expr: map_expr_dag(function, expr, compress), form, only_integral_type

    )

  File "/home/isaac/.conda/envs/fenicsx-env/lib/python3.13/site-packages/ufl/algorithms/map_integrands.py", line 30, in map_integrands

    map_integrands(function, itg, only_integral_type) for itg in form.integrals()

    ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

  File "/home/isaac/.conda/envs/fenicsx-env/lib/python3.13/site-packages/ufl/algorithms/map_integrands.py", line 39, in map_integrands

    return itg.reconstruct(function(itg.integrand()))

                           ~~~~~~~~^^^^^^^^^^^^^^^^^

  File "/home/isaac/.conda/envs/fenicsx-env/lib/python3.13/site-packages/ufl/algorithms/map_integrands.py", line 87, in <lambda>

    lambda expr: map_expr_dag(function, expr, compress), form, only_integral_type

                 ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^

  File "/home/isaac/.conda/envs/fenicsx-env/lib/python3.13/site-packages/ufl/corealg/map_dag.py", line 35, in map_expr_dag

    (result,) = map_expr_dags(

                ~~~~~~~~~~~~~^

        function, [expression], compress=compress, vcache=vcache, rcache=rcache

        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    )

    ^

  File "/home/isaac/.conda/envs/fenicsx-env/lib/python3.13/site-packages/ufl/corealg/map_dag.py", line 101, in map_expr_dags

    r = handlers[v._ufl_typecode_](v)

  File "/home/isaac/.conda/envs/fenicsx-env/lib/python3.13/site-packages/ufl/algorithms/apply_restrictions.py", line 61, in _require_restriction

    raise ValueError(f"Discontinuous type {o._ufl_class_.__name__} must be restricted.")

ValueError: Discontinuous type Jacobian must be restricted.

Some more information, about the mesh, it comes from generating it from this file.geo:

// Gmsh project created on Sat Mar  8 22:37:56 2025
SetFactory("OpenCASCADE");
//+
Mesh.CharacteristicLengthMax = 0.0009;
Mesh.CharacteristicLengthMin = 0.0001;

Disk(1) = {0, 0, 0, 0.05, 0.05};
//+
Disk(2) = {0, 0, 0, 0.035, 0.035};


BooleanDifference{ Surface{1}; Delete;}{ Surface{2}; Delete;}
// Surface 3 is the full annulus. I need to split it into 2.

Point(4) = {0, 0.05, 0};
//+
Point(5) = {0, 0.035, 0};
//+
// Origin.
Point(6) = {0, 0, 0};


Line(4) = {2,3};
Line(5) = {4,5};

Circle(6) = {5, 6, 2};

Circle(7) = {4, 6, 3};
Curve Loop(8) = {5,6,4,7};




Plane Surface(9) = {8};

// 3-quarter surface
BooleanDifference{ Surface{1}; Delete;}{ Surface{9};}

BooleanFragments{ Surface{1}; Surface{9}; Delete; }{ }

Physical Surface("three_quarter_material", 20) = {1};
Physical Surface("quarter_material", 21) = {9};
Physical Curve("theta_0", 22) = {4};
Physical Curve("theta_pi_over_2", 23) = {5};
Physical Curve("r_inner_quarter", 24) = {6};
Physical Curve("r_outer_quarter", 25) = {7};
Physical Curve("r_inner_three_quarter", 26) = {8};
Physical Curve("r_outer_three_quarter", 27) = {9};

Maybe it is faulty but I don’t see it?
I read it with


class TheMesh:
    def __init__(self, mesh_file="meshes/quarter_annulus.msh") -> None:
        self.mesh, self.cell_markers, self.facet_markers = gmshio.read_from_msh(mesh_file, MPI.COMM_WORLD, gdim=2)
        self.x = SpatialCoordinate(self.mesh)
        self.dx = Measure("dx", domain=self.mesh, subdomain_data=self.cell_markers)
        self.ds = Measure("ds", domain=self.mesh, subdomain_data=self.facet_markers)
        self.dS = Measure("dS", domain=self.mesh, subdomain_data=self.facet_markers)
        self.n = FacetNormal(self.mesh)

My whole code is kind of gigantic, the MWE would be big too, since I have to recreate the tensor part, the mixed element part, etc.
I realize it’s very hard to offer guidance, but any idea is still welcome.

It doesn’t seem like you have restricted the FacetNormal (n) in either case

As you are working on an interior facet (dS) measure, the normal can go in either direction.

For one-sided integrals, see for instance

1 Like

Thanks a lot dokken, that worked like a charm!