API reference#

Every public function is also importable from the top-level fenicsx_compat package.

Version checks#

fenicsx_compat._dolfinx_version.at_least(v: str) → bool[source]#

Whether the installed dolfinx is at least version v.

fenicsx_compat._dolfinx_version.before(v: str) → bool[source]#

Whether the installed dolfinx is strictly before version v.

Meshes#

fenicsx_compat.mesh.cmap(mesh: Mesh) → CoordinateElement[source]#

Get the coordinate element for a mesh’s geometry.

dolfinx 0.10 has only the geometry.cmap property. From 0.11, geometry.cmaps exists and cmap emits a deprecation warning, so prefer cmaps[0] where it exists. Dispatches on hasattr.

fenicsx_compat.mesh.dofmap(mesh: Mesh) → ndarray[tuple[Any, ...], dtype[int32]][source]#

Get the geometry dofmap for a mesh, across the same dofmaps split as cmap.

fenicsx_compat.mesh.form_map(form: Form) → tuple[IndexMap, int][source]#

Get the index map and block size for a linear form’s test space.

FunctionSpace.dofmaps is a method on dolfinx 0.10 and a sequence from 0.11. Dispatches on the TypeError raised by calling it.

For a mesh with a single cell type, form.function_spaces[0].dofmap gives the same on every version without this function.

fenicsx_compat.mesh.meshtags_topology(tags: MeshTags) → Topology[source]#

Get the topology a MeshTags is defined on, as the Python dolfinx.mesh.Topology.

dolfinx 0.10 and 0.11 return the cpp topology from MeshTags.topology; dolfinx PR #4350 (post-0.11) returns the Python wrapper. The cpp object is the wrapper’s _cpp_object on every version. Dispatches on isinstance.

fenicsx_compat.mesh.cell_permutation_info(topology: Topology) → ndarray[tuple[Any, ...], dtype[uint32]][source]#

Compute and return the packed per-cell permutation info, ghost cells included.

dolfinx 0.10 and 0.11 compute it, together with the facet permutations, in a single argument-free topology.create_entity_permutations(). dolfinx PR #3904 (post-0.11) split that into create_cell_permutations() and a per-dimension create_entity_permutations(dim). Dispatches on hasattr.

fenicsx_compat.mesh.facet_permutations(topology: Topology) → ndarray[tuple[Any, ...], dtype[uint8]][source]#

Compute and return the permutation of every facet of every cell.

Shape (num_cells, num_facets_per_cell), ghost cells included. Each value encodes the facet’s orientation as seen from the cell, as used for FFCx’s quadrature_permutation.

Across the same dolfinx PR #3904 split as cell_permutation_info: 0.10 and 0.11 expose these via get_facet_permutations() after an argument-free create_entity_permutations(); from #3904 they are get_entity_permutations(tdim - 1) after create_entity_permutations(tdim - 1). Both return a flat array.

fenicsx_compat.mesh.create_facet_orientations(topology: Topology) → None[source]#

Compute the facet orientations that contravariant Piola spaces on a manifold need.

dolfinx PR #4625 (post-0.11) refuses to create a Raviart-Thomas or other contravariant Piola space on a manifold until topology.create_facet_orientations() has been called, and that method does not exist on 0.10 and 0.11, which create such spaces without it. Calls it where it exists and does nothing otherwise. Dispatches on hasattr.

Call it before creating the space, on every rank: it is collective, and changing the orientations afterwards invalidates functions on existing spaces. Where it exists it raises ValueError unless the topological dimension is 2, as the native method does.

fenicsx_compat.mesh.create_cell_partitioner(ghost_mode: GhostMode, max_facet_to_cell_links: int = 2) → Callable[source]#

Build a cell partitioner with ghost mode baked in, for dolfinx 0.10 and 0.11.

Not needed (and not called) on dolfinx generations where create_cell_partitioner was removed (post dolfinx PR #4403) — create_mesh takes ghost_mode directly on those. Raises NotImplementedError if called anyway, so a caller doesn’t silently get an un-ghosted mesh.

fenicsx_compat.mesh.create_mesh(comm: Comm, cells: ndarray[tuple[Any, ...], dtype[int64]], e, x: ndarray[tuple[Any, ...], dtype[floating]], *, partitioner: Callable | None = None, ghost_mode: GhostMode = GhostMode.shared_facet, max_facet_to_cell_links: int = 2, num_threads: int = 1) → Mesh[source]#

Create a mesh from topology and geometry arrays, with a given ghost mode.

Post dolfinx PR #4403 (post-0.11), ghost_mode and num_threads are create_mesh’s own kwargs and create_cell_partitioner is gone. On 0.10 and 0.11 the ghost mode must be baked into the partitioner via create_cell_partitioner; num_threads is ignored there. Dispatches on inspect.signature.

If partitioner is given explicitly it is always used as-is (never silently replaced); ghost_mode is still passed to create_mesh on generations that take it.

The default ghost_mode is shared_facet, while dolfinx’s own default is none on every version.

fenicsx_compat.mesh.reconstruct_mesh(mesh: Mesh, coordinate_element_degree: int) → Mesh[source]#

Copy a mesh, changing its coordinate element degree.

The topology is shared with the original mesh; the geometry is reconstructed. Delegates to the native dolfinx.fem.interpolate_geometry from dolfinx>=0.11; for 0.10, reconstructs the geometry manually (no native equivalent exists at that version).

fenicsx_compat.mesh.transfer_meshtags_to_submesh(entity_tag: MeshTags, submesh: Mesh, cell_to_parent, vertex_to_parent) → MeshTags[source]#

Transfer mesh tags from a parent mesh to a submesh.

Native since dolfinx 0.11.0 (dolfinx PR #4172, merged 2026-04-23, confirmed absent at the v0.10.0 tag). Raises NotImplementedError on 0.10 — scifem’s own pre-0.11 fallback calls a compiled _scifem nanobind extension, and fenicsx-compat ships no compiled extensions (see fenicsx-compat design spec §2, §9 item 1).

IMPORTANT: native dolfinx’s own positional argument order for cell_to_parent/vertex_to_parent is NOT stable across the versions this wrapper supports. v0.11.0’s signature is (entity_tag, submesh, vertex_to_parent, cell_to_parent); nightly (0.12.0.dev0) swapped the two to (entity_tag, submesh, cell_to_parent, vertex_to_parent). The parameter names are identical across the swap, so this wrapper forwards to native by keyword, never positionally — that is correct on both versions regardless of which one is installed. Forwarding positionally in nightly’s order (as an earlier revision of this function did) is silently wrong on 0.11: it hands native the cell map where it expects the vertex map and vice versa, which does not raise a Python exception but corrupts internal state and crashes the process with SIGTERM (observed on this project’s stable/0.11 CI leg). Do not “simplify” this back to a positional call.

This wrapper’s own declared parameter order, (entity_tag, submesh, cell_to_parent, vertex_to_parent), is fixed and does not change with the installed dolfinx version — chosen to match nightly’s order and create_submesh’s (entity/cell map, vertex map) return order, so callers can unpack create_submesh’s output and forward it directly.

Finite elements and interpolation#

fenicsx_compat.fem.real_functionspace(mesh: Mesh, value_shape: tuple[int, ...] = ()) → FunctionSpace[source]#

Create a real (single-constant-per-domain) function space.

This is a deliberate exception to this project’s prefer-introspection rule (spec §4): basix.ufl.real_element exists and constructs successfully on both sides of the dolfinx 0.10/0.11 boundary, so hasattr cannot distinguish them. What actually differs is deeper: at dolfinx v0.10.0 the element real_element returns is a non-functional stub whose basix_element property raises a bare, message-less NotImplementedError (confirmed by the v0.10.0 CI traceback: dolfinx.fem.functionspace accesses ufl_e.basix_element while building the space, which is exactly where it dies). From dolfinx 0.11 that property works and dolfinx.fem.functionspace succeeds (confirmed locally on this install, dolfinx 0.12.0.dev0). Since no hasattr/signature probe can see into that property without constructing a whole function space, we gate on at_least("0.11.0") instead - a hard version check, used here only because introspection genuinely cannot distinguish the behaviour. Do not “fix” this back to an introspection-only check.

Separately, and unrelated to the above, real_element gained a dtype parameter in basix 0.11 (basix PR #1001). That part is detected by inspect.signature, and dtype= is only passed when the installed signature has that parameter.

The hasattr(basix.ufl, "real_element") guard below covers a different failure mode: basix is a separate, independently installable package from dolfinx, so it can be version-skewed against it. A clear NotImplementedError beats an AttributeError deeper in the call.

fenicsx_compat.fem.finite_element_ctor_kwargs(constructor, basix_element, *, gdim: int, value_shape: tuple[int, ...] | None, symmetric: bool = False)[source]#

Construct a single-element cpp FiniteElement, across the gdim addition.

dolfinx PR #4511 (post-0.11) added the mesh’s geometric dimension as a required constructor argument, needed for elements on manifolds. 0.10 and 0.11 take (element, block_shape, symmetric) only, and gdim is ignored there. Dispatches on the TypeError nanobind raises for the unmatched overload, before any work is done.

constructor is dolfinx.cpp.fem.FiniteElement_float32 or _float64; value_shape is passed as dolfinx’s block_shape (None for an unblocked element).

fenicsx_compat.fem.interpolate(cpp_function, values: ndarray[tuple[Any, ...], dtype[_ScalarT]], cells: ndarray[tuple[Any, ...], dtype[int32]]) → None[source]#

Interpolate raw values into a cpp Function, across the interpolate_f/interpolate rename.

fenicsx_compat.fem.expression_eval(expr, domain, entity: ndarray[tuple[Any, ...], dtype[int32]]) → ndarray[tuple[Any, ...], dtype[_ScalarT]][source]#

Evaluate an Expression at integration entities.

Facet entities are a 2D [[cell, local_entity]] array from dolfinx PR #3625, which is in 0.10, so expr.eval(domain, entity) works the same on every supported version. The retry with the entities flattened is for the 1D shape of older dolfinx.

fenicsx_compat.fem.permute_facet_quadrature(facet_type: CellType, points: ndarray[tuple[Any, ...], dtype[floating]]) → list[ndarray[tuple[Any, ...], dtype[floating]]][source]#

Build the per-permutation facet quadrature point table.

Entry p holds the points to use for a facet with permutation p, so that an Expression evaluated on a facet is evaluated at points as seen from the cell. Before dolfinx PR #4140 (dolfinx<0.11.0.dev0), facet Expressions are not permuted at all, so every entry is points. From 0.11.0.dev0, dolfinx and FFCx (FFCx PR #836) map the points through the facet’s global orientation, so each entry is points with the inverse of that permutation applied, computed with ffcx.ir.elementtables (an internal, non-public FFCx module).

fenicsx_compat.fem.permute_interpolation_data(data: ndarray[tuple[Any, ...], dtype[_ScalarT]], integration_entities: ndarray[tuple[Any, ...], dtype[int32]], cell_permutation_info: ndarray[tuple[Any, ...], dtype[uint32]], basix_element, facet_type: CellType) → None[source]#

In-place permute interpolation data for the pre-dolfinx-PR-#4140 dof ordering.

No-op from dolfinx>=0.11.0.dev0, where dolfinx applies this permutation internally.

fenicsx_compat.fem.interpolate_to_submesh_entity_maps(volume_function: Function, points: ndarray[tuple[Any, ...], dtype[floating]], entity_maps=None) → Expression[source]#

Build an Expression, across the entity_maps-kwarg-existence split on Expression.

entity_maps is passed from dolfinx PR #4140 (0.11) on. dolfinx 0.10 has no such argument, so there it is dropped.

Index maps and vectors#

fenicsx_compat.la.create_index_map(comm, num_local: int, ghosts: ndarray[tuple[Any, ...], dtype[int64]], owners: ndarray[tuple[Any, ...], dtype[int32]], tag: int = 0) → IndexMap[source]#

Build an IndexMap, across the class-constructor-to-factory-function rename.

dolfinx PR #4496 (post-0.11) replaced the dolfinx.common.IndexMap class constructor with a dolfinx.common.index_map factory taking ghosts as a single (ghosts, owners) tuple kwarg, and returning the new Python IndexMap instead of the cpp one. The tag argument (used internally for MPI communication) is caller-specific — pass a value unique across index maps that may communicate concurrently.

fenicsx_compat.la.unwrap_index_map(index_map) → IndexMap[source]#

Return the underlying cpp IndexMap, whether given the Python wrapper or the cpp object itself.

fenicsx_compat.la.index_to_dest_ranks(index_map, tag: int) → tuple[ndarray[tuple[Any, ...], dtype[int32]], ndarray[tuple[Any, ...], dtype[int32]]][source]#

The ranks sharing each local index of index_map, as (ranks, offsets).

Ranks sharing local index i are ranks[offsets[i]:offsets[i + 1]]: for an owned index, the ranks that ghost it; for a ghost, its owner and the other ranks that ghost it. The calling rank is excluded. Collective.

dolfinx 0.10 and 0.11 take an MPI tag for the consensus exchange and return an AdjacencyList. dolfinx PR #4354 (post-0.11) made it return a (ranks, offsets) tuple, and #4573 dropped the tag. tag is only used on the older versions; pass a value no other exchange in flight uses. Accepts either the Python wrapper or the cpp IndexMap. Dispatches on the TypeError raised while binding the arguments, which happens on every rank before any communication, so it cannot deadlock.

fenicsx_compat.la.vector(index_map, bs: int, dtype: type[~typing.Any] | ~numpy.dtype[~typing.Any] | ~numpy._typing._dtype_like._SupportsDType[~numpy.dtype[~typing.Any]] | tuple[~typing.Any, ~typing.Any] | list[~typing.Any] | ~numpy._typing._dtype_like._DTypeDict | str | None = <class 'numpy.float64'>) → Vector[source]#

Create a distributed vector compatible with an index map and block size.

dtype must stay keyword — it became keyword-only from dolfinx 0.12 (dolfinx PR #4489). Pass index_map as dolfinx returns it: from dolfinx PR #4496 dolfinx.la.vector needs the Python IndexMap, and a cpp one raises AttributeError. Never pass scatterer — dolfinx 0.11 has no such parameter.

fenicsx_compat.la.create_sub_index_map(index_map, indices: ndarray[tuple[Any, ...], dtype[int32]], *, allow_owner_change: bool = False) → tuple[IndexMap, ndarray[tuple[Any, ...], dtype[int32]]][source]#

Build an index map for a subset of the local indices of index_map.

Returns (sub_map, sub_to_parent): the sub-map, and the local index in index_map of each of its local indices. indices must be sorted and unique, as dolfinx 0.10 and 0.11 require. An index included by a rank that ghosts it but not by its owner has to change owner; unless allow_owner_change, that raises RuntimeError. Collective.

dolfinx 0.10 and 0.11 have this only as dolfinx.cpp.common.create_sub_index_map(map, indices, allow_owner_change), which throws on the owning rank alone. dolfinx PR #4479 (post-0.11) dropped the flag in favour of always allowing the change and returning a rank-local flag saying whether one happened, and #4496 added the Python dolfinx.common.create_sub_index_map. There the flag is reduced here before raising, so every rank raises together. Accepts either the Python wrapper or the cpp IndexMap; sub_map is whichever type the installed dolfinx returns. Dispatches on hasattr.

fenicsx_compat.la.local_range(index: int, N: int, size: int) → tuple[int, int][source]#

The half-open range of [0, N) held by block index of size almost-equal blocks.

The first N % size blocks hold one index more than the rest, the split dolfinx itself uses to distribute indices over ranks. dolfinx 0.10 and 0.11 do not expose the free function dolfinx.common.local_range to Python (only the IndexMap property of the same name); it is reproduced here where it is missing. Dispatches on hasattr.

PETSc#

fenicsx_compat.petsc.zero_petsc_vector(b: Vec) → None[source]#

Zero a PETSc vector, including ghosts.

fenicsx_compat.petsc.ghost_update(x: Vec, insert_mode, scatter_mode) → None[source]#

Ghost-update a PETSc vector.

fenicsx_compat.petsc.set_bc(b: Vec, bcs, x0=None, alpha: float = 1.0) → None[source]#

Set boundary condition values in a vector, across the nested-vs-single-form set_bc API.

fenicsx_compat.petsc.apply_lifting_and_set_bc(b: Vec, a, bcs, x: Vec | None = None, alpha: float = 1.0) → None[source]#

Apply lifting to a vector and set boundary conditions.

Convenience function to apply lifting and set boundary conditions for single, blocked, or nested forms. Modifies b such that its free rows become b - alpha * A (g - x) and its constrained rows alpha * (g - x), with g the boundary values and A the operator of a.

Parameters:
  • b – The vector to apply lifting to.

  • a – Form or nested sequence of forms to apply lifting from.

  • bcs – The boundary conditions to apply.

  • x – Vector to subtract from the boundary conditions (e.g. in a Newton iteration).

  • alpha – Scaling factor for the boundary conditions.

fenicsx_compat.petsc.bcs_by_block(spaces: Sequence[FunctionSpace | None], bcs: Sequence[DirichletBC]) → list[list[DirichletBC]][source]#

Arrange boundary conditions by the space they constrain, across dolfinx 0.11/0.12.

dolfinx.fem.bcs_by_block only works when the spaces and bc.function_space are the same flavour: both cpp objects on 0.10 and 0.11, both Python wrappers from dolfinx PRs #4342 and #4350. That holds for spaces from dolfinx.fem.extract_function_spaces, but not for spaces the caller builds a list of. Normalizing both sides to cpp objects and calling the cpp contains directly accepts either flavour on every version.

UFL#

fenicsx_compat.ufl.domain_of(expr)[source]#

Get the single UFL domain an expression is defined on.

ufl.domain.extract_domains is available across the whole 0.10+ floor; the expr.ufl_domain() fallback found duplicated across the source repos (some with a fallback, some without — see spec §5.6) is below-floor legacy and is not ported.

fenicsx_compat.ufl.apply_pullback_inverse(pullback, expr, domain)[source]#

Map expr from the physical cell to the reference cell.

Uses pullback.apply_inverse, added in FEniCS/ufl PR #511 (first released in UFL 2026.2.0), when the installed UFL has it. The UFL releases dolfinx 0.10 and 0.11 pin (2025.2, 2026.1) lack it, so a closed form is used for the identity, contravariant Piola and covariant Piola pullbacks; any other pullback raises NotImplementedError there. Dispatches on hasattr.

Input and output#

fenicsx_compat.io.vtk_cell_type(cell_type: CellType, num_nodes: int) → int[source]#

The VTK cell type identifier of a cell with num_nodes geometry nodes.

dolfinx 0.10 and 0.11 have dolfinx.cpp.io.get_vtk_cell_type(cell, dim), which returns the identifier of the dimension-dim sub-entity of cell. dolfinx PR #4621 (post-0.11) replaced dim with the number of nodes, so that it can tell the serendipity layouts apart and reject node counts no Lagrange layout has; passing the topological dimension there raises for quadrilaterals and hexahedra. For a Lagrange layout both return the same arbitrary-degree Lagrange identifier. On 0.10 and 0.11 num_nodes is not checked, and the whole cell’s identifier is returned.

Neither the argument count nor inspect.signature tells the two apart, so this dispatches on the parameter name in the nanobind signature, i.e. the first line of the docstring.

fenicsx_compat.io.resolve_adios_scope(adios2)[source]#

Resolve the ADIOS2 Python module scope, across the adios2.bindings submodule split.

fenicsx_compat.io.pyvista_allow_snake_case(pyvista) → None[source]#

Allow snake_case VTK attribute access.

Across pyvista’s _VTK_SNAKE_CASE_STATE rename (pyvista 0.47+).

fenicsx_compat.io.model_to_mesh(model, comm, rank: int, gdim: int = 3, *, partitioner=None, ghost_mode: ~dolfinx.cpp.mesh.GhostMode = GhostMode.shared_facet, max_facet_to_cell_links: int = 2, dtype=<class 'numpy.float64'>)[source]#

Create a mesh from a gmsh model, with a given ghost mode.

Returns the dolfinx.io.gmsh.MeshData that dolfinx builds. Across the same dolfinx PR #4403 split as create_mesh: from there, model_to_mesh takes ghost_mode and threads it into whichever partitioner is used. On 0.10 and 0.11 the ghost mode can only reach the mesh through a partitioner built with it, so one is built here when partitioner is not given. dolfinx 0.10 also has no max_facet_to_cell_links parameter on model_to_mesh, so there it only takes effect through that partitioner. Dispatches on inspect.signature.

The default ghost_mode is shared_facet, as for create_mesh, while dolfinx’s own default is none on every version.

If partitioner is given explicitly it is always used as-is. On 0.10 and 0.11, ghost_mode (and on 0.10 max_facet_to_cell_links) then only apply if that partitioner was built with them.

Optional dependencies#

fenicsx_compat.optional.require_module(name: str, *, extra: str | None = None) → ModuleType[source]#

Import name, raising a clear, actionable ImportError if it’s missing.