Poisson equation#

Download sources

This demo illustrates how to:

  • Solve a simple Helmholtz problem on a mixed-topology mesh.

  • Create a mesh from numpy arrays using dolfinx.mesh.create_mesh()

In development

Mixed-topology meshes are a work in progress and are not yet fully supported in DOLFINx.

import sys
import typing

from mpi4py import MPI

import numpy as np
from scipy.sparse.linalg import spsolve

import basix
import dolfinx.cpp as _cpp
import ufl
from dolfinx.cpp.fem import locate_dofs_geometrical
from dolfinx.cpp.mesh import GhostMode, create_mesh
from dolfinx.fem import (
    FiniteElement,
    FunctionSpace,
    assemble_matrix,
    assemble_vector,
    coordinate_element,
    create_dofmaps,
    dirichletbc,
    mixed_topology_form,
)
from dolfinx.io.utils import cell_perm_vtk
from dolfinx.mesh import CellType, Mesh, Topology
from dolfinx.mesh import _create_cell_partitioner_from_ghost_mode as _cell_partitioner
if MPI.COMM_WORLD.size > 1:
    print("Not yet running in parallel")
    sys.exit(0)

Create a mixed-topology mesh#

nx = 16
ny = 16
nz = 16
n_cells = nx * ny * nz

cells: list = [[], []]
orig_idx: list = [[], []]
geom = []

if MPI.COMM_WORLD.rank == 0:
    idx = 0
    for i in range(n_cells):
        iz = i // (nx * ny)
        j = i % (nx * ny)
        iy = j // nx
        ix = j % nx

        v0 = (iz * (ny + 1) + iy) * (nx + 1) + ix
        v1 = v0 + 1
        v2 = v0 + (nx + 1)
        v3 = v1 + (nx + 1)
        v4 = v0 + (nx + 1) * (ny + 1)
        v5 = v1 + (nx + 1) * (ny + 1)
        v6 = v2 + (nx + 1) * (ny + 1)
        v7 = v3 + (nx + 1) * (ny + 1)
        if ix < nx / 2:
            cells[0] += [v0, v1, v2, v3, v4, v5, v6, v7]
            orig_idx[0] += [idx]
            idx += 1
        else:
            cells[1] += [v0, v1, v2, v4, v5, v6]
            orig_idx[1] += [idx]
            idx += 1
            cells[1] += [v1, v2, v3, v5, v6, v7]
            orig_idx[1] += [idx]
            idx += 1

    n_points = (nx + 1) * (ny + 1) * (nz + 1)
    sqxy = (nx + 1) * (ny + 1)
    for v in range(n_points):
        iz = v // sqxy
        p = v % sqxy
        iy = p // (nx + 1)
        ix = p % (nx + 1)
        geom += [[ix / nx, iy / ny, iz / nz]]

cells_np = [np.array(c) for c in cells]
geomx = np.array(geom, dtype=np.float64)
hexahedron = coordinate_element(CellType.hexahedron, 1)
prism = coordinate_element(CellType.prism, 1)

part = _cell_partitioner(GhostMode.none, 2)
mesh = create_mesh(
    MPI.COMM_WORLD,
    cells_np,
    [
        typing.cast(_cpp.fem.CoordinateElement_float64, hexahedron._cpp_object),
        typing.cast(_cpp.fem.CoordinateElement_float64, prism._cpp_object),
    ],
    geomx,
    part,
    2,
    1,
)

Create a mixed-topology dofmap and function space#

Create elements and dofmaps for each cell type

elements = [
    basix.create_element(basix.ElementFamily.P, basix.CellType.hexahedron, 1),
    basix.create_element(basix.ElementFamily.P, basix.CellType.prism, 1),
]
dolfinx_elements = [
    FiniteElement(
        _cpp.fem.FiniteElement_float64(
            typing.cast(basix._basixcpp.FiniteElement_float64, e._e), None, False
        )
    )
    for e in elements
]
# NOTE: Both dofmaps have the same IndexMap, but different cell_dofs
dofmaps = create_dofmaps(
    mesh.comm,
    Topology(mesh.topology),
    dolfinx_elements,
)

# Create C++ function space
V_cpp = _cpp.fem.FunctionSpace_float64(
    mesh,
    [e._cpp_object for e in dolfinx_elements],  # type: ignore[misc]
    [dofmap._cpp_object for dofmap in dofmaps],
)


# Select some BCs
def marker(x):
    """BC Selector."""
    return np.logical_or(np.isclose(x[2], 0.0), np.isclose(x[2], 1.0))


# dirichletbc needs a function space that carries a UFL domain, to
# associate one with the (uniform) boundary value. UFL does not yet
# support mixed-topology domains (see the FIXME below), so wrap V_cpp
# with an arbitrarily chosen cell type's domain/element -- neither is
# used for anything beyond this association.
domain = ufl.Mesh(basix.ufl.element("Lagrange", "hexahedron", 1, shape=(3,)))
element = basix.ufl.wrap_element(elements[0])
V = FunctionSpace(Mesh(mesh, domain), element, V_cpp)

bcdofs = locate_dofs_geometrical(V_cpp, marker)
bc = dirichletbc(value=0.0, dofs=bcdofs, V=V)

Creating and compiling a variational formulation#

We create the variational forms for each cell type. FIXME: This hack is required at the moment because UFL does not yet know about mixed topology meshes.

a = []
L = []
for i, cell_name in enumerate(["hexahedron", "prism"]):
    print(f"Creating form for {cell_name}")
    element = basix.ufl.wrap_element(elements[i])
    domain = ufl.Mesh(basix.ufl.element("Lagrange", cell_name, 1, shape=(3,)))
    V = FunctionSpace(Mesh(mesh, domain), element, V_cpp)
    u, v = ufl.TrialFunction(V), ufl.TestFunction(V)
    k = 12.0
    x = ufl.SpatialCoordinate(domain)
    a += [(ufl.inner(ufl.grad(u), ufl.grad(v)) - k**2 * u * v) * ufl.dx]
    f = ufl.sin(ufl.pi * x[0]) * ufl.sin(ufl.pi * x[1])
    L += [f * v * ufl.dx]

Compile the form FIXME: For the time being, since UFL doesn’t understand mixed topology meshes, we have to call mixed_topology_form instead of form.

a_form = mixed_topology_form(a, dtype=np.float64)
L_form = mixed_topology_form(L, dtype=np.float64)

Assembling and solving the linear system#

We use the native matrix and vector format in DOLFINx to assemble the left and right hand side of the linear system.

A = assemble_matrix(a_form, bcs=[bc])
b = assemble_vector(L_form)
bc.set(b.array)

We use scipy.sparse.linalg.spsolve() to solve the resulting linear system

A_scipy = A.to_scipy()
b_scipy = b.array
x_scipy = spsolve(A_scipy, b_scipy)
print(f"Solution vector norm {np.linalg.norm(x_scipy)}")

Mixed-topology I/O We manually build a ASCII XDMF file to store the mesh and solution NOTE: this should be replaced with VTKHDF

xdmf = """<?xml version="1.0"?>
<!DOCTYPE Xdmf SYSTEM "Xdmf.dtd" []>
<Xdmf Version="3.0" xmlns:xi="https://www.w3.org/2001/XInclude">
  <Domain>
    <Grid Name="mesh" GridType="Collection" CollectionType="spatial">

"""

perm = [cell_perm_vtk(CellType.hexahedron, 8), cell_perm_vtk(CellType.prism, 6)]
topologies = ["Hexahedron", "Wedge"]

for j in range(2):
    vtk_topology = []
    geom_dm = mesh.geometry.dofmaps[j]
    for c in geom_dm:
        vtk_topology += list(c[perm[j]])
    topology_type = topologies[j]

    xdmf += f"""
      <Grid Name="{topology_type}" GridType="Uniform">
        <Topology TopologyType="{topology_type}">
          <DataItem Dimensions="{geom_dm.shape[0]} {geom_dm.shape[1]}"
           Precision="4" NumberType="Int" Format="XML">
          {" ".join(str(val) for val in vtk_topology)}
          </DataItem>
        </Topology>
        <Geometry GeometryType="XYZ" NumberType="float" Rank="2" Precision="8">
          <DataItem Dimensions="{mesh.geometry.x.shape[0]} 3" Format="XML">
            {" ".join(str(val) for val in mesh.geometry.x.flatten())}
          </DataItem>
        </Geometry>
        <Attribute Name="u" Center="Node" NumberType="float" Precision="8">
          <DataItem Dimensions="{len(x_scipy)}" Format="XML">
            {" ".join(str(val) for val in x_scipy)}
          </DataItem>
       </Attribute>
      </Grid>"""

xdmf += """
    </Grid>
  </Domain>
</Xdmf>
"""

with open("mixed-mesh.xdmf", "w") as fd:
    fd.write(xdmf)