FEniCS / ufl

UFL - Unified Form Language

Home Page:https://fenicsproject.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Form compiler has to compute `CellVolume`

jorgensd opened this issue · comments

Is there any specific reason for the following code:

@memoized_handler
def cell_volume(self, o):
if self._preserve_types[o._ufl_typecode_]:
return o
domain = o.ufl_domain()
if not domain.is_piecewise_linear_simplex_domain():
# Don't lower for non-affine cells, instead leave it to
# form compiler
warnings.warn("Only know how to compute the cell volume of an affine cell.")
return o
r = self.jacobian_determinant(JacobianDeterminant(domain))
r0 = ReferenceCellVolume(domain)
return abs(r * r0)

To me, it seems like this should work for both affine and non-affine cells. I tested this locally with DOLFINx (by commenting out the if clause, with the following code:

import gmsh
import numpy as np
import ufl
from dolfinx import fem, io
from mpi4py import MPI

# Generate a mesh with non-affine cells
gdim = 2
mesh_comm = MPI.COMM_WORLD
model_rank = 0
if mesh_comm.rank == model_rank:
    gmsh.initialize()
    rectangle = gmsh.model.occ.addRectangle(0, 0, 0, 1, 1, tag=1)
    obstacle = gmsh.model.occ.addDisk(0.5, 0.5, 0, 0.3, 0.3)
    fluid = gmsh.model.occ.cut([(gdim, rectangle)], [(gdim, obstacle)])
    gmsh.model.occ.synchronize()
    volumes = gmsh.model.getEntities(dim=gdim)
    gmsh.model.addPhysicalGroup(volumes[0][0], [volumes[0][1]], 1)
    gmsh.option.setNumber("Mesh.Algorithm", 8)
    gmsh.option.setNumber("Mesh.RecombinationAlgorithm", 2)
    gmsh.option.setNumber("Mesh.RecombineAll", 1)
    gmsh.option.setNumber("Mesh.SubdivisionAlgorithm", 1)
    gmsh.model.mesh.generate(gdim)
domain, _, _ = io.gmshio.model_to_mesh(
    gmsh.model, mesh_comm, model_rank, gdim=gdim)
gmsh.finalize()


Ve = fem.FunctionSpace(domain, ("DG", 0))
elmVol_ufl = ufl.CellVolume(domain)
elmVol_expr = fem.Expression(elmVol_ufl, Ve.element.interpolation_points())
elmVol = fem.Function(Ve, name="Element Volume (expression)")
elmVol.interpolate(elmVol_expr)

q_degree = 3
vol2 = fem.Function(Ve)
fem.petsc.assemble_vector(vol2.vector, fem.form(
    1*ufl.TestFunction(Ve)*ufl.dx(metadata={"quadrature_degree": q_degree})))
vol2.x.scatter_forward()
print(vol2.x.array - elmVol.x.array)
assert np.allclose(vol2.x.array, elmVol.x.array)
vol2.name = "ElementVolume (assembly)"

with io.XDMFFile(domain.comm, "cell_vol.xdmf", "w") as xdmf:
    xdmf.write_mesh(domain)
    xdmf.write_function(elmVol)
    xdmf.write_function(vol2)

Yielding
cellvolume

That lowering is a one point quadrature for computing the cell volume. If your cell is a way from affine, this is a bad approximation I think.

That lowering is a one point quadrature for computing the cell volume. If your cell is a way from affine, this is a bad approximation I think.

How can you see that the lowering is to a single point quadrature? (I know the expression interpolation in DOLFINx is equivalent to a one point quadrature, but that is not the main point here).

I cannot spot that assumption in the referenced ufl code, as I would expect JacobianDeterminant to be what we use in general for integral scaling of non-affine and affine geometries, but maybe I've missed something.

That lowering is a one point quadrature for computing the cell volume. If your cell is a way from affine, this is a bad approximation I think.

Oh, right. It is because this doesn't give the form compiler any instructions on summing the jacobians for each quadrature point before multiplying by anything else.