add cpp stub files - #4096
Conversation
|
Nice! Fixes #3238.
No need, top-level will override anyways. |
|
Thanks for doing this! We should sanity check the output particularly for NDArrays in/out - wjakob/nanobind#1155 |
|
Output looks good to me, for example Details"""Mesh library module"""
from collections.abc import Callable, Sequence
import enum
from typing import Annotated, overload
import numpy
from numpy.typing import NDArray
import dolfinx.cpp.common
import dolfinx.cpp.fem
import dolfinx.cpp.graph
class CellType(enum.Enum):
point = 1
interval = 2
triangle = 3
quadrilateral = -4
tetrahedron = 4
pyramid = -5
prism = -6
hexahedron = -8
@property
def name(self) -> object: ...
def to_type(cell: str) -> CellType: ...
def to_string(type: CellType) -> str: ...
def is_simplex(type: CellType) -> bool: ...
def cell_entity_type(type: CellType, dim: int, index: int) -> CellType: ...
def cell_dim(type: CellType) -> int: ...
def cell_num_entities(type: CellType, dim: int) -> int: ...
def cell_num_vertices(type: CellType) -> int: ...
def get_entity_vertices(type: CellType, dim: int) -> dolfinx.cpp.graph.AdjacencyList_int32: ...
def extract_topology(cell_type: CellType, layout: dolfinx.cpp.fem.ElementDofLayout, cells: Annotated[NDArray[numpy.int64], dict(shape=(None,), order='C', writable=False)]) -> NDArray[numpy.int64]: ...
@overload
def build_dual_graph(comm: MPICommWrapper, cell_type: CellType, cells: dolfinx.cpp.graph.AdjacencyList_int64, max_facet_to_cell_links: int | None) -> dolfinx.cpp.graph.AdjacencyList_int64:
"""Build dual graph for cells"""
@overload
def build_dual_graph(comm: MPICommWrapper, cell_types: Sequence[CellType], cells: Sequence[Annotated[NDArray[numpy.int64], dict(shape=(None,), order='C', writable=False)]], max_facet_to_cell_links: int | None) -> dolfinx.cpp.graph.AdjacencyList_int64: ...
class GhostMode(enum.Enum):
none = 0
shared_facet = 1
def compute_entities(topology: Topology, dim: int, entity_type: CellType, num_threads: int = 1) -> tuple[list[dolfinx.cpp.graph.AdjacencyList_int32], dolfinx.cpp.graph.AdjacencyList_int32, dolfinx.cpp.common.IndexMap, list[int]]: ...
def compute_connectivity(topology: Topology, d0: Sequence[int], d1: Sequence[int]) -> list[dolfinx.cpp.graph.AdjacencyList_int32]: ...
class EntityMap:
"""EntityMap object"""
def __init__(self, topology: Topology, sub_topology: Topology, dim: int, sub_topology_to_topology: Annotated[NDArray[numpy.int32], dict(shape=(None,), order='C', writable=False)]) -> None: ...
def sub_topology_to_topology(self, entities: Annotated[NDArray[numpy.int32], dict(shape=(None,), order='C', writable=False)], inverse: bool) -> NDArray[numpy.int32]: ...
@property
def dim(self) -> int: ...
@property
def topology(self) -> Topology: ...
@property
def sub_topology(self) -> Topology: ...
class Topology:
"""Topology object"""
def __init__(self, cell_type: CellType, vertex_map: dolfinx.cpp.common.IndexMap, cell_map: dolfinx.cpp.common.IndexMap, cells: dolfinx.cpp.graph.AdjacencyList_int32, original_index: Annotated[NDArray[numpy.int64], dict(shape=(None,), order='C', writable=False)] | None) -> None: ...
def create_entities(self, dim: int, num_threads: int = 1) -> bool: ...
def create_entity_permutations(self) -> None: ...
def create_connectivity(self, d0: int, d1: int) -> None: ...
def get_facet_permutations(self) -> Annotated[NDArray[numpy.uint8], dict(writable=False)]: ...
def get_cell_permutation_info(self) -> Annotated[NDArray[numpy.uint32], dict(writable=False)]: ...
@property
def dim(self) -> int:
"""Topological dimension"""
@property
def original_cell_index(self) -> Annotated[NDArray[numpy.int64], dict(writable=False)]: ...
@original_cell_index.setter
def original_cell_index(self, original_cell_indices: Annotated[NDArray[numpy.int64], dict(shape=(None,), order='C', writable=False)]) -> None: ...
@property
def original_cell_indices(self) -> list[Annotated[NDArray[numpy.int64], dict(writable=False)]]: ...
@overload
def connectivity(self, d0: int, d1: int) -> dolfinx.cpp.graph.AdjacencyList_int32: ...
@overload
def connectivity(self, d0: Sequence[int], d1: Sequence[int]) -> dolfinx.cpp.graph.AdjacencyList_int32: ...
def index_map(self, dim: int) -> dolfinx.cpp.common.IndexMap: ...
def index_maps(self, dim: int) -> list[dolfinx.cpp.common.IndexMap]: ...
@property
def cell_type(self) -> CellType: ...
@property
def cell_types(self) -> list[CellType]: ...
@property
def entity_types(self) -> list[list[CellType]]: ...
def interprocess_facets(self) -> Annotated[NDArray[numpy.int32], dict(writable=False)]: ...
@property
def comm(self) -> MPICommWrapper: ...
def create_topology(arg0: MPICommWrapper, arg1: Sequence[CellType], arg2: Sequence[Sequence[int]], arg3: Sequence[Sequence[int]], arg4: Sequence[Sequence[int]], arg5: Sequence[int], /) -> Topology:
"""Create a Topology object."""
def compute_mixed_cell_pairs(arg0: Topology, arg1: CellType, /) -> list[list[int]]: ...
class MeshTags_int8:
"""MeshTags object"""
def __init__(self, arg0: Topology, arg1: int, arg2: Annotated[NDArray[numpy.int32], dict(shape=(None,), order='C', writable=False)], arg3: Annotated[NDArray[numpy.int8], dict(shape=(None,), order='C', writable=False)], /) -> None: ...
@property
def dtype(self) -> str: ...
@property
def name(self) -> str: ...
@name.setter
def name(self, arg: str, /) -> None: ...
@property
def dim(self) -> int: ...
@property
def topology(self) -> Topology: ...
@property
def values(self) -> Annotated[NDArray[numpy.int8], dict(writable=False)]: ...
@property
def indices(self) -> Annotated[NDArray[numpy.int32], dict(writable=False)]: ...
def find(self, arg: int, /) -> NDArray[numpy.int32]: ...
@overload
def create_meshtags(arg0: Topology, arg1: int, arg2: dolfinx.cpp.graph.AdjacencyList_int32, arg3: Annotated[NDArray[numpy.int8], dict(shape=(None,), order='C', writable=False)], /) -> MeshTags_int8: ...
@overload
def create_meshtags(arg0: Topology, arg1: int, arg2: dolfinx.cpp.graph.AdjacencyList_int32, arg3: Annotated[NDArray[numpy.int32], dict(shape=(None,), order='C', writable=False)], /) -> MeshTags_int32: ...
@overload
def create_meshtags(arg0: Topology, arg1: int, arg2: dolfinx.cpp.graph.AdjacencyList_int32, arg3: Annotated[NDArray[numpy.int64], dict(shape=(None,), order='C', writable=False)], /) -> MeshTags_int64: ...
@overload
def create_meshtags(arg0: Topology, arg1: int, arg2: dolfinx.cpp.graph.AdjacencyList_int32, arg3: Annotated[NDArray[numpy.float64], dict(shape=(None,), order='C', writable=False)], /) -> MeshTags_float64: ...
class MeshTags_int32:
"""MeshTags object"""
def __init__(self, arg0: Topology, arg1: int, arg2: Annotated[NDArray[numpy.int32], dict(shape=(None,), order='C', writable=False)], arg3: Annotated[NDArray[numpy.int32], dict(shape=(None,), order='C', writable=False)], /) -> None: ...
@property
def dtype(self) -> str: ...
@property
def name(self) -> str: ...
@name.setter
def name(self, arg: str, /) -> None: ...
@property
def dim(self) -> int: ...
@property
def topology(self) -> Topology: ...
@property
def values(self) -> Annotated[NDArray[numpy.int32], dict(writable=False)]: ...
@property
def indices(self) -> Annotated[NDArray[numpy.int32], dict(writable=False)]: ...
def find(self, arg: int, /) -> NDArray[numpy.int32]: ...
class MeshTags_int64:
"""MeshTags object"""
def __init__(self, arg0: Topology, arg1: int, arg2: Annotated[NDArray[numpy.int32], dict(shape=(None,), order='C', writable=False)], arg3: Annotated[NDArray[numpy.int64], dict(shape=(None,), order='C', writable=False)], /) -> None: ...
@property
def dtype(self) -> str: ...
@property
def name(self) -> str: ...
@name.setter
def name(self, arg: str, /) -> None: ...
@property
def dim(self) -> int: ...
@property
def topology(self) -> Topology: ...
@property
def values(self) -> Annotated[NDArray[numpy.int64], dict(writable=False)]: ...
@property
def indices(self) -> Annotated[NDArray[numpy.int32], dict(writable=False)]: ...
def find(self, arg: int, /) -> NDArray[numpy.int32]: ...
class MeshTags_float64:
"""MeshTags object"""
def __init__(self, arg0: Topology, arg1: int, arg2: Annotated[NDArray[numpy.int32], dict(shape=(None,), order='C', writable=False)], arg3: Annotated[NDArray[numpy.float64], dict(shape=(None,), order='C', writable=False)], /) -> None: ...
@property
def dtype(self) -> str: ...
@property
def name(self) -> str: ...
@name.setter
def name(self, arg: str, /) -> None: ...
@property
def dim(self) -> int: ...
@property
def topology(self) -> Topology: ...
@property
def values(self) -> Annotated[NDArray[numpy.float64], dict(writable=False)]: ...
@property
def indices(self) -> Annotated[NDArray[numpy.int32], dict(writable=False)]: ...
def find(self, arg: float, /) -> NDArray[numpy.int32]: ...
class Geometry_float32:
"""Geometry object"""
def __init__(self, index_map: dolfinx.cpp.common.IndexMap, dofmap: Annotated[NDArray[numpy.int32], dict(shape=(None, None), order='C', writable=False)], element: dolfinx.cpp.fem.CoordinateElement_float32, x: Annotated[NDArray[numpy.float32], dict(shape=(None, None), writable=False)], input_global_indices: Annotated[NDArray[numpy.int64], dict(shape=(None,), order='C', writable=False)]) -> None: ...
@property
def dim(self) -> int:
"""Geometric dimension"""
@property
def dofmap(self) -> Annotated[NDArray[numpy.int32], dict(writable=False)]: ...
def dofmaps(self, i: int) -> Annotated[NDArray[numpy.int32], dict(writable=False)]:
"""
Get the geometry dofmap associated with coordinate element i (mixed topology)
"""
def index_map(self) -> dolfinx.cpp.common.IndexMap: ...
@property
def x(self) -> Annotated[NDArray[numpy.float32], dict(shape=(None, 3))]:
"""
Return coordinates of all geometry points. Each row is the coordinate of a point.
"""
@property
def cmap(self) -> dolfinx.cpp.fem.CoordinateElement_float32:
"""The coordinate map"""
def cmaps(self, arg: int, /) -> dolfinx.cpp.fem.CoordinateElement_float32:
"""The ith coordinate map"""
@property
def input_global_indices(self) -> Annotated[NDArray[numpy.int64], dict(writable=False)]: ...
class Mesh_float32:
"""Mesh object"""
def __init__(self, comm: MPICommWrapper, topology: Topology, geometry: Geometry_float32) -> None: ...
@property
def geometry(self) -> Geometry_float32:
"""Mesh geometry"""
@property
def topology(self) -> Topology:
"""Mesh topology"""
@property
def comm(self) -> MPICommWrapper: ...
@property
def name(self) -> str: ...
@name.setter
def name(self, arg: str, /) -> None: ...
def create_interval_float32(comm: MPICommWrapper, n: int, p: Sequence[float], ghost_mode: GhostMode, partitioner: Callable[[MPICommWrapper, int, Sequence[CellType], Sequence[Annotated[NDArray[numpy.int64], dict(writable=False)]]], dolfinx.cpp.graph.AdjacencyList_int32] | None) -> Mesh_float32: ...
def create_rectangle_float32(comm: MPICommWrapper, p: Sequence[Sequence[float]], n: Sequence[int], celltype: CellType, partitioner: Callable[[MPICommWrapper, int, Sequence[CellType], Sequence[Annotated[NDArray[numpy.int64], dict(writable=False)]]], dolfinx.cpp.graph.AdjacencyList_int32] | None, diagonal: DiagonalType) -> Mesh_float32: ...
def create_box_float32(comm: MPICommWrapper, p: Sequence[Sequence[float]], n: Sequence[int], celltype: CellType, partitioner: Callable[[MPICommWrapper, int, Sequence[CellType], Sequence[Annotated[NDArray[numpy.int64], dict(writable=False)]]], dolfinx.cpp.graph.AdjacencyList_int32] | None) -> Mesh_float32: ...
@overload
def create_mesh(arg0: MPICommWrapper, arg1: Sequence[Annotated[NDArray[numpy.int64], dict(shape=(None,), order='C', writable=False)]], arg2: Sequence[dolfinx.cpp.fem.CoordinateElement_float32], arg3: Annotated[NDArray[numpy.float32], dict(order='C', writable=False)], arg4: Callable[[MPICommWrapper, int, Sequence[CellType], Sequence[Annotated[NDArray[numpy.int64], dict(writable=False)]]], dolfinx.cpp.graph.AdjacencyList_int32], arg5: int | None) -> Mesh_float32: ...
@overload
def create_mesh(comm: MPICommWrapper, cells: Annotated[NDArray[numpy.int64], dict(shape=(None, None), order='C', writable=False)], element: dolfinx.cpp.fem.CoordinateElement_float32, x: Annotated[NDArray[numpy.float32], dict(order='C', writable=False)], partitioner: Callable[[MPICommWrapper, int, Sequence[CellType], Sequence[Annotated[NDArray[numpy.int64], dict(writable=False)]]], dolfinx.cpp.graph.AdjacencyList_int32] | None, max_facet_to_cell_links: int | None) -> Mesh_float32:
"""Helper function for creating meshes."""
@overload
def create_mesh(arg0: MPICommWrapper, arg1: Sequence[Annotated[NDArray[numpy.int64], dict(shape=(None,), order='C', writable=False)]], arg2: Sequence[dolfinx.cpp.fem.CoordinateElement_float64], arg3: Annotated[NDArray[numpy.float64], dict(order='C', writable=False)], arg4: Callable[[MPICommWrapper, int, Sequence[CellType], Sequence[Annotated[NDArray[numpy.int64], dict(writable=False)]]], dolfinx.cpp.graph.AdjacencyList_int32], arg5: int | None) -> Mesh_float64: ...
@overload
def create_mesh(comm: MPICommWrapper, cells: Annotated[NDArray[numpy.int64], dict(shape=(None, None), order='C', writable=False)], element: dolfinx.cpp.fem.CoordinateElement_float64, x: Annotated[NDArray[numpy.float64], dict(order='C', writable=False)], partitioner: Callable[[MPICommWrapper, int, Sequence[CellType], Sequence[Annotated[NDArray[numpy.int64], dict(writable=False)]]], dolfinx.cpp.graph.AdjacencyList_int32] | None, max_facet_to_cell_links: int | None) -> Mesh_float64:
"""Helper function for creating meshes."""
@overload
def create_submesh(mesh: Mesh_float32, dim: int, entities: Annotated[NDArray[numpy.int32], dict(shape=(None,), order='C', writable=False)]) -> tuple[Mesh_float32, EntityMap, EntityMap, NDArray[numpy.int32]]: ...
@overload
def create_submesh(mesh: Mesh_float64, dim: int, entities: Annotated[NDArray[numpy.int32], dict(shape=(None,), order='C', writable=False)]) -> tuple[Mesh_float64, EntityMap, EntityMap, NDArray[numpy.int32]]: ...
@overload
def cell_normals(mesh: Mesh_float32, dim: int, entities: Annotated[NDArray[numpy.int32], dict(shape=(None,), order='C', writable=False)]) -> NDArray[numpy.float32]: ...
@overload
def cell_normals(mesh: Mesh_float64, dim: int, entities: Annotated[NDArray[numpy.int32], dict(shape=(None,), order='C', writable=False)]) -> NDArray[numpy.float64]: ...
@overload
def h(mesh: Mesh_float32, dim: int, entities: Annotated[NDArray[numpy.int32], dict(shape=(None,), order='C', writable=False)]) -> NDArray[numpy.float32]:
"""Compute maximum distsance between any two vertices."""
@overload
def h(mesh: Mesh_float64, dim: int, entities: Annotated[NDArray[numpy.int32], dict(shape=(None,), order='C', writable=False)]) -> NDArray[numpy.float64]: ...
@overload
def compute_midpoints(mesh: Mesh_float32, dim: int, entities: Annotated[NDArray[numpy.int32], dict(shape=(None,), order='C', writable=False)]) -> NDArray[numpy.float32]: ...
@overload
def compute_midpoints(mesh: Mesh_float64, dim: int, entities: Annotated[NDArray[numpy.int32], dict(shape=(None,), order='C', writable=False)]) -> NDArray[numpy.float64]: ...
@overload
def locate_entities(mesh: Mesh_float32, dim: int, marker: Callable[[Annotated[NDArray[numpy.float32], dict(shape=(None, None), writable=False)]], Annotated[NDArray[numpy.bool_], dict(shape=(None,), order='C')]]) -> NDArray[numpy.int32]: ...
@overload
def locate_entities(mesh: Mesh_float32, dim: int, marker: Callable[[Annotated[NDArray[numpy.float32], dict(shape=(None, None), writable=False)]], Annotated[NDArray[numpy.bool_], dict(shape=(None,), order='C')]], entity_type_idx: int) -> NDArray[numpy.int32]: ...
@overload
def locate_entities(mesh: Mesh_float64, dim: int, marker: Callable[[Annotated[NDArray[numpy.float64], dict(shape=(None, None), writable=False)]], Annotated[NDArray[numpy.bool_], dict(shape=(None,), order='C')]]) -> NDArray[numpy.int32]: ...
@overload
def locate_entities(mesh: Mesh_float64, dim: int, marker: Callable[[Annotated[NDArray[numpy.float64], dict(shape=(None, None), writable=False)]], Annotated[NDArray[numpy.bool_], dict(shape=(None,), order='C')]], entity_type_idx: int) -> NDArray[numpy.int32]: ...
@overload
def locate_entities_boundary(mesh: Mesh_float32, dim: int, marker: Callable[[Annotated[NDArray[numpy.float32], dict(shape=(None, None), writable=False)]], Annotated[NDArray[numpy.bool_], dict(shape=(None,), order='C')]]) -> NDArray[numpy.int32]: ...
@overload
def locate_entities_boundary(mesh: Mesh_float64, dim: int, marker: Callable[[Annotated[NDArray[numpy.float64], dict(shape=(None, None), writable=False)]], Annotated[NDArray[numpy.bool_], dict(shape=(None,), order='C')]]) -> NDArray[numpy.int32]: ...
@overload
def entities_to_geometry(mesh: Mesh_float32, dim: int, entities: Annotated[NDArray[numpy.int32], dict(shape=(None,), order='C', writable=False)], permute: bool) -> NDArray[numpy.int32]: ...
@overload
def entities_to_geometry(mesh: Mesh_float64, dim: int, entities: Annotated[NDArray[numpy.int32], dict(shape=(None,), order='C', writable=False)], permute: bool) -> NDArray[numpy.int32]: ...
@overload
def create_geometry(arg0: Topology, arg1: Sequence[dolfinx.cpp.fem.CoordinateElement_float32], arg2: Annotated[NDArray[numpy.int64], dict(shape=(None,), order='C', writable=False)], arg3: Annotated[NDArray[numpy.int64], dict(shape=(None,), order='C', writable=False)], arg4: Annotated[NDArray[numpy.float32], dict(shape=(None,), order='C', writable=False)], arg5: int, /) -> Geometry_float32: ...
@overload
def create_geometry(arg0: Topology, arg1: Sequence[dolfinx.cpp.fem.CoordinateElement_float64], arg2: Annotated[NDArray[numpy.int64], dict(shape=(None,), order='C', writable=False)], arg3: Annotated[NDArray[numpy.int64], dict(shape=(None,), order='C', writable=False)], arg4: Annotated[NDArray[numpy.float64], dict(shape=(None,), order='C', writable=False)], arg5: int, /) -> Geometry_float64: ...
class Geometry_float64:
"""Geometry object"""
def __init__(self, index_map: dolfinx.cpp.common.IndexMap, dofmap: Annotated[NDArray[numpy.int32], dict(shape=(None, None), order='C', writable=False)], element: dolfinx.cpp.fem.CoordinateElement_float64, x: Annotated[NDArray[numpy.float64], dict(shape=(None, None), writable=False)], input_global_indices: Annotated[NDArray[numpy.int64], dict(shape=(None,), order='C', writable=False)]) -> None: ...
@property
def dim(self) -> int:
"""Geometric dimension"""
@property
def dofmap(self) -> Annotated[NDArray[numpy.int32], dict(writable=False)]: ...
def dofmaps(self, i: int) -> Annotated[NDArray[numpy.int32], dict(writable=False)]:
"""
Get the geometry dofmap associated with coordinate element i (mixed topology)
"""
def index_map(self) -> dolfinx.cpp.common.IndexMap: ...
@property
def x(self) -> Annotated[NDArray[numpy.float64], dict(shape=(None, 3))]:
"""
Return coordinates of all geometry points. Each row is the coordinate of a point.
"""
@property
def cmap(self) -> dolfinx.cpp.fem.CoordinateElement_float64:
"""The coordinate map"""
def cmaps(self, arg: int, /) -> dolfinx.cpp.fem.CoordinateElement_float64:
"""The ith coordinate map"""
@property
def input_global_indices(self) -> Annotated[NDArray[numpy.int64], dict(writable=False)]: ...
class Mesh_float64:
"""Mesh object"""
def __init__(self, comm: MPICommWrapper, topology: Topology, geometry: Geometry_float64) -> None: ...
@property
def geometry(self) -> Geometry_float64:
"""Mesh geometry"""
@property
def topology(self) -> Topology:
"""Mesh topology"""
@property
def comm(self) -> MPICommWrapper: ...
@property
def name(self) -> str: ...
@name.setter
def name(self, arg: str, /) -> None: ...
def create_interval_float64(comm: MPICommWrapper, n: int, p: Sequence[float], ghost_mode: GhostMode, partitioner: Callable[[MPICommWrapper, int, Sequence[CellType], Sequence[Annotated[NDArray[numpy.int64], dict(writable=False)]]], dolfinx.cpp.graph.AdjacencyList_int32] | None) -> Mesh_float64: ...
def create_rectangle_float64(comm: MPICommWrapper, p: Sequence[Sequence[float]], n: Sequence[int], celltype: CellType, partitioner: Callable[[MPICommWrapper, int, Sequence[CellType], Sequence[Annotated[NDArray[numpy.int64], dict(writable=False)]]], dolfinx.cpp.graph.AdjacencyList_int32] | None, diagonal: DiagonalType) -> Mesh_float64: ...
def create_box_float64(comm: MPICommWrapper, p: Sequence[Sequence[float]], n: Sequence[int], celltype: CellType, partitioner: Callable[[MPICommWrapper, int, Sequence[CellType], Sequence[Annotated[NDArray[numpy.int64], dict(writable=False)]]], dolfinx.cpp.graph.AdjacencyList_int32] | None) -> Mesh_float64: ...
@overload
def create_cell_partitioner(mode: GhostMode, max_facet_to_cell_links: int | None) -> Callable[[MPICommWrapper, int, list[CellType], list[Annotated[NDArray[numpy.int64], dict(writable=False)]]], dolfinx.cpp.graph.AdjacencyList_int32]:
"""Create default cell partitioner."""
@overload
def create_cell_partitioner(part: Callable[[MPICommWrapper, int, dolfinx.cpp.graph.AdjacencyList_int64, bool], dolfinx.cpp.graph.AdjacencyList_int32], ghost_mode: GhostMode, max_facet_to_cell_links: int | None) -> Callable[[MPICommWrapper, int, list[CellType], list[Annotated[NDArray[numpy.int64], dict(writable=False)]]], dolfinx.cpp.graph.AdjacencyList_int32]:
"""Create a cell partitioner from a graph partitioning function."""
def exterior_facet_indices(topology: Topology) -> NDArray[numpy.int32]: ...
def compute_incident_entities(mesh: Topology, entities: Annotated[NDArray[numpy.int32], dict(shape=(None,), order='C', writable=False)], d0: int, d1: int) -> NDArray[numpy.int32]: ...
class DiagonalType(enum.Enum):
left = 0
right = 1
crossed = 2
left_right = 4
right_left = 5 |
Seems to be not triggered for us. Maybe because of the |
schnellerhase
left a comment
There was a problem hiding this comment.
Should go in after FEniCS/basix#991.
Note that these OUTPUT are not actually passed to the stub generator and purely used for dependency management within CMake.
|
Now should go in after FEniCS/basix#970 |
|
Looks good - as an aside we need to guard the Spack build cache uploads for PRs coming from external repositories. |
|
FYI, downstream checks in a nightly docker image are failing with I guess that the issue is that |
|
Thanks - I think this is not being picked up because our mypy checks are before the compile-time generation of the typing stubs. Can you confirm @francesco-ballarin ? |
|
This CI step dolfinx/.github/workflows/ccpp.yml Line 135 in 05b41ec |
|
Going to revert this see #4100 - needs a bit more debugging and testing, e.g. editable installs. |
* add cpp stub files * fix nanobind OUTPUT path Note that these OUTPUT are not actually passed to the stub generator and purely used for dependency management within CMake.
|
This should be re-worked following the implementation in FEniCS/basix#992 which doesn't require e.g. However, I do see an issue with using https://github.com/jorgensd/dolfinx_mpc/blob/main/python/CMakeLists.txt#L72 |
|
We might be best off patching nanobind to allow more control: https://github.com/wjakob/nanobind/blob/master/cmake/nanobind-config.cmake#L703 |
* add cpp stub files * fix nanobind OUTPUT path Note that these OUTPUT are not actually passed to the stub generator and purely used for dependency management within CMake.
* add cpp stub files * fix nanobind OUTPUT path Note that these OUTPUT are not actually passed to the stub generator and purely used for dependency management within CMake.
* add cpp stub files * fix nanobind OUTPUT path Note that these OUTPUT are not actually passed to the stub generator and purely used for dependency management within CMake.
* add cpp stub files * fix nanobind OUTPUT path Note that these OUTPUT are not actually passed to the stub generator and purely used for dependency management within CMake.
* add cpp stub files * fix nanobind OUTPUT path Note that these OUTPUT are not actually passed to the stub generator and purely used for dependency management within CMake.
) * add cpp stub files (FEniCS#4096) * add cpp stub files * fix nanobind OUTPUT path Note that these OUTPUT are not actually passed to the stub generator and purely used for dependency management within CMake. * Fix: reserved python global keyword * Fix: non-install time for UNIX platforms + CI adaptation * Fix: scoped log import * fix: petsc... * Work in progress on autogenerating nanobind stubs * Generate dolfinx.cpp stubs automatically * Add a way to disable nanobind stubgen (e.g. HPC builds) * Fix formatting * Fix on platforms without petsc4py * Use spack-fenics due to updated base deps * Back to main * Fix. * Revert * Fix Windows module name * Fix mypy failures surfaced by nanobind stub generation The auto-generated dolfinx.cpp stubs made mypy see real, precise types for the compiled extension for the first time, surfacing ~446 errors in the "Build and test" CI job (the only mypy invocation that actually installs dolfinx before running mypy, so the only one exercising the generated stubs). Root causes and fixes: - Unix stub generation imported the compiled module as bare `cpp` instead of `dolfinx.cpp`, so nanobind wrote cross-submodule references like `import cpp.la`, which doesn't exist and silently resolved to Any under mypy, masking real errors and producing bogus "overload can never match" diagnostics. Stage the built module under a throwaway dolfinx/ package dir before invoking nanobind_add_stub so the module's real __name__ is dolfinx.cpp. - `_IntegralType`/`MPICommWrapper` bindings used names or const_name() values with no resolvable Python type, breaking stub cross-refs. - `FiniteElement`/`AdjacencyList` `__eq__` bindings exposed the raw C++ operator==, violating object.__eq__'s Liskov contract; wrapped in a lambda with an isinstance guard instead. - Dead, unreachable overloads (complex instantiations of interpolation_matrix/discrete_curl/discrete_gradient that are always shadowed by the real ones, and a redundant read_geometry_data registration) removed. - Missing nanobind/stl/map.h and .../string.h includes were causing stubgen to fall back to invalid raw C++ type strings. - ~250 Python-side errors are the same runtime-safe-but-statically- unverifiable scalar-type dispatch pattern already handled elsewhere in this PR (fem/petsc.py); fixed with matching `# type: ignore`. A handful were real bugs instead: wrong return-type annotations in forms.py, and dispatch-table locals missing an explicit Union annotation. - The ~24 remaining errors are genuine ecosystem-level gaps confirmed via isolated repros (mypy can't disambiguate numpy dtype/rank in NDArray annotations; nanobind has no std::reference_wrapper caster; basix's C++ types aren't stub-resolvable across the package boundary) rather than dolfinx bugs. Suppressed per-file via a new nanobind stub pattern file (stub_patterns.txt) that injects a scoped `# mypy: disable-error-code=...` into just the affected generated stubs. Verified: mypy clean (matching the failing job's exact PETSc/ADIOS2 config), ruff/clang-format clean, and a second full build with PETSc+SLEPc+ADIOS2+ParMETIS+SuperLU_DIST (-Werror) compiles cleanly with runtime sanity checks passing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Extend mypy type-ignore fixes for new/changed main-branch APIs Post-merge fixups: main added a mesh argument to pack_coefficients, an interpolate_geometry function, and reshaped a few overload call sites (dofmaps as a sequence instead of a method, apply_lifting's now-arg-type instead of call-overload mismatch). Same runtime-safe/ statically-unverifiable scalar-dispatch pattern as the rest of this branch; verified mypy clean again afterwards. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Format python/CMakeLists.txt with gersemi Fixes Lint CI failure introduced by the main merge, which brought in the gersemi CMake formatter (replacing cmake-format). Purely cosmetic reformatting of the nanobind stub-generation additions; no logic changes. * Fix mypy errors only visible in package-mode type checking The "Build and test" CI job runs mypy in package mode (`mypy -p dolfinx`) against a genuinely installed dolfinx with real nanobind-generated stubs, unlike the Lint job's `mypy dolfinx`, which never builds/installs dolfinx and so resolves dolfinx.cpp.* as Any via ignore_missing_imports, masking overload-resolution errors entirely. Package mode surfaced 34 real errors invisible in Lint mode: - bcs.py/assemble.py: the DirichletBC/insert_diagonal overload mismatches report as call-overload in package mode, not the arg-type code the ignores were scoped to; switch to codeless ignores since the reported code is unstable across the two modes. - io/utils.py: new arg-type error on write_function's getattr cast. - mesh.py: refine()'s partitioner annotation didn't include None, even though the docstring documents None as a valid value and callers (test_refinement.py) pass it. - test_mesh_partitioners.py: overly narrow inferred list element type broke when a ParameterSet skip-marker was appended. - test_mesh.py: partitioner_kahip/partitioner_parmetis are only present on the compiled module when built with those optional backends, which mypy can't know statically. - demo_mixed-topology.py, demo_static-condensation.py: same dtype-union-overload ambiguity pattern fixed elsewhere in this PR. Verified via a from-scratch venv replicating the CI job exactly (Python 3.12, non-editable install, real generated stubs): mypy passes in all three invocation modes (Lint's mypy dolfinx/test/demo, and package-mode -p dolfinx/test/demo), ruff check/format clean, and the affected test files pass under pytest. * Also ignore partitioner_scotch attr-defined in test_mesh.py The CI runner for the "Build and test" job doesn't have libscotch installed, so partitioner_scotch is absent from the compiled module there too (unlike my local reproduction, which has SCOTCH via Homebrew) -- same build-config-dependent attribute-existence issue as partitioner_kahip/partitioner_parmetis fixed in the previous commit. * Remove two avoidable mypy ignores - fem/utils.py: narrow space0/space1's cpp objects via a match statement in interpolation_matrix so mypy can resolve the matching interpolation_matrix overload directly, instead of ignoring the call. Also gives callers a clear TypeError on mismatched dtypes instead of an opaque nanobind overload-resolution failure. - io/gmsh.py: read_from_msh's partitioner parameter typed its callback's 4th argument as AdjacencyList (the pure-Python wrapper, unused elsewhere in this file) instead of _AdjacencyList_int32 (the cpp type that model_to_mesh, which it delegates to, actually expects). Fixing the annotation removes the genuine type mismatch instead of suppressing it. * Switch VTKFile/XDMFFile to composition instead of subclassing cpp types VTKFile and XDMFFile subclassed their nanobind-bound counterparts directly (_cpp.io.VTKFile/_cpp.io.XDMFFile), unlike Mesh, Function, FunctionSpace, and VTXWriter, which all wrap their cpp object via a _cpp_object attribute instead. Subclassing meant every overridden method that re-types its arguments from the raw cpp type to the friendlier Python wrapper type (Mesh vs Mesh_float32/64, etc.) was a genuine Liskov substitution violation from mypy's point of view, requiring a # type: ignore[override] on write_mesh, write_meshtags, write_function, and read_meshtags. Switching to composition removes the inheritance relationship, so these methods are no longer checked against a base signature and the four ignores are gone with no suppression left behind. This requires explicit delegation for every previously-inherited method actually used elsewhere (close, comm, flush, write_information, read_information, read_topology_data, read_geometry_data, read_cell_type), confirmed via a grep across tests and demos, plus write_geometry for API parity even though nothing in-tree calls it. read_meshtags's and the new write_geometry's underlying cpp functions only support float64 meshes/geometry; narrowed via isinstance instead of suppressing, giving a real static check and a clear TypeError for the unsupported case instead of an ignore. * Remove 3 more avoidable mypy ignores in io/vtkhdf.py - read_mesh's filename was passed straight through as str | Path, but the cpp read_vtkhdf_mesh_float32/64 bindings only accept str; cast explicitly instead of ignoring, narrowing the float32 branch's ignore down to just the pre-existing, unrelated assignment error. - variant (mesh_cpp.geometry.cmaps[0].variant) is a raw int from the cpp binding; basix.ufl.element expects a LagrangeVariant. Wrap it, matching the same conversion already used in fem/utils.py's interpolate_geometry. - cell_types[0].name's ignore was stale: removing it produces no error, confirmed by rebuilding and rerunning mypy. Found via a full audit: stripped every remaining arg-type ignore in the tree at once, rebuilt against a from-scratch venv mirroring CI's package-mode mypy check, and inspected every resulting error. Outside of this file, everything else needs its ignore back - each is a single or multiple _cpp_object-typed argument whose Python-level type is a plain dtype union unconnected to any generic parameter (same pattern as FunctionSpace/Mesh/Form discussed elsewhere), so narrowing would mean adding an isinstance/match branch per dtype for no real safety benefit. Left those as ignores rather than trade a suppression for genuine complexity. * Remove remaining ignore[assignment] in vtkhdf.read_mesh mesh_cpp's type was inferred from its first assignment (read_vtkhdf_mesh_float64 -> Mesh_float64), so the second branch's Mesh_float32 assignment was flagged as incompatible. Declare the union type explicitly up front, matching the same pattern already used for the analogous float32/float64 dispatch in mesh.py's create_interval/create_rectangle/create_box. * Remove 5 stale arg-type ignores Found via a systematic strip-and-rebuild audit of every remaining ignore[arg-type] comment: these 5 produce no mypy error at all once removed, in package mode, mypy test, and mypy demo. No code changes beyond deleting the comments. * Fix wrong partitioner Callable signature in gmsh.py model_to_mesh's and read_from_msh's partitioner parameter was typed as Callable[[Comm, int, int, AdjacencyList_int32], AdjacencyList_int32], but that value is passed straight into create_mesh, whose cpp binding requires Callable[[Comm, int, Sequence[CellType], Sequence[NDArray[int64]]], AdjacencyList_int32] - matching what dolfinx.mesh.create_cell_partitioner's return type already documents. The 3rd/4th argument types were simply wrong, not an inherent dtype-overload ambiguity. * Remove 2 arg-type ignores in geometry.bb_tree Branch on isinstance(mesh._cpp_object, Mesh_float32/64) instead of np.issubdtype(mesh.geometry.x.dtype, ...) - same if/elif structure, but discriminating on the actual value being passed to the cpp constructor lets mypy narrow it through each branch. * Remove arg-type ignore in mesh.create_geometry The function already validates that element.dtype matches x.dtype before construction, so element._cpp_object's own concrete type already determines which Geometry class to build - dispatch off isinstance(cpp_element, ...) directly instead of a separate ftype lookup keyed on x.dtype. Removes a layer of indirection rather than adding one. Incidental fix: the "Unknown floating type for geometry" message was missing its f-string prefix, so {x.dtype} was never interpolated. * Remove 2 arg-type ignores in mesh.create_point_mesh geometry was just constructed from points.dtype a few lines above, so geometry._cpp_object's own concrete type already determines which Mesh class to build; branch on isinstance(cpp_geometry, ...) instead of points.dtype == ... for the same reason as the bb_tree/ create_geometry fixes. * Remove arg-type ignore in mesh.create_cell_partitioner @singledispatch requires the base function's part parameter to be typed Callable | GhostMode (a supertype of the registered GhostMode variant), but the base implementation is only ever reached when part is genuinely a Callable - a GhostMode argument gets routed to the registered variant instead. Add a defensive isinstance guard so mypy narrows part to Callable for the rest of the function, matching what was already true at runtime. * Remove 2 arg-type ignores in graph.comm_graph_data/comm_to_json Both are non-overloaded cpp functions accepting only AdjacencyList_int_sizet_int8__int32_int32, and the only Python-level producer of that type is comm_graph() (confirmed via the cpp stub: its return type is unconditionally that one class). Add an isinstance guard, giving callers a clear error instead of an opaque nanobind overload failure if they pass an AdjacencyList from anywhere else. * Remove arg-type ignore in LinearProblem's preconditioner form form()'s parameter was typed ufl.Form | Sequence[ufl.Form] | Sequence[Sequence[ufl.Form]] with no None, even though LinearProblem's P (the preconditioner) is documented and typed as optional, and _create_form's implementation already passes None straight through unchanged via its final `else: return form` branch. Widen the annotation to match what the implementation already does. * Remove arg-type ignore in derivative_block's rank-one Jacobian branch The rank-zero branch already guards u with isinstance(u, Function) / isinstance(u, Sequence), raising a clear ValueError otherwise. The rank-one branch called _derive_univariate_jacobian(F, u, du) (which requires u: Function, du: ufl.Argument | None) with no equivalent guard, so a caller passing u or du as a sequence would previously fall through silently instead of getting the same clear error the sibling branch gives. * Remove arg-type ignore in LinearProblem.solve mypy type-checks functools.singledispatch calls only against the base function's signature (L: typing.Any, constants: npt.NDArray | None, ...), so passing self.L (a Form) as the second positional argument to assemble_vector(self.b.array, self.L) was checked against constants: npt.NDArray | None and flagged. self.b.array/self.L are concretely npt.NDArray/Form here (not ambiguous unions), so calling the registered variant _assemble_vector_array directly - the exact function singledispatch would have dispatched to anyway - resolves cleanly. Same pattern already used in fem/petsc.py for the same functools.singledispatch/mypy limitation. * Remove 6 arg-type ignores in fem/petsc.py assemble_vector/assemble_matrix functools.singledispatch base functions recursively called their own singledispatch wrapper to reach the registered PETSc.Vec/PETSc.Mat variant, but mypy only checks singledispatch calls against the base signature - which is shaped for the "no b/A supplied yet" case, not the (b, L, ...)/(A, a, ...) shape actually being passed. Name the two previously-anonymous registered variants (_assemble_vector_petsc, _assemble_matrix_petsc) and call them directly at each of the 6 call sites (the base function's own recursive call, the NEST sub-block recursion, and 4 call sites in LinearProblem/assemble_residual/ NewtonSolverNonlinearProblem), bypassing the mismatched base check entirely. Also fixes a genuine pre-existing bug: the base assemble_matrix's constants/coeffs parameter types didn't match its own a: Form | Sequence[Sequence[Form]] shape (Sequence[X]/Sequence[dict] instead of bare X/Sequence[Sequence[dict]]), inconsistent with the correctly-typed registered sibling - this was the source of 2 of the 6 errors surviving the rename alone. Adds one small isinstance(coeffs, dict) guard in the NEST-matrix branch, mirroring the existing isinstance(a, Sequence) check right above it, for a genuinely-reachable misuse case. Verified against a from-scratch PETSc-enabled build of this worktree (petsc4py isn't available in the mypy-checking venv used elsewhere in this branch's history): assemble_vector/assemble_matrix in both their new-object and existing-object forms, LinearProblem.solve(), assemble_residual, NewtonSolverNonlinearProblem.J, NEST-matrix assembly, and the new guard's error path. * Remove 3 more arg-type ignores in fem/petsc.py assemble_vector Same root cause as assemble_matrix's NEST branch: constants/coeffs still carry their full declared union type (including shapes meant for the other branches) at each _assemble_vector_array call site, since narrowing L doesn't narrow the separate constants/coeffs parameters. Add isinstance guards mirroring the existing isinstance(L, Sequence) checks - a bare dict in the NEST/block branches, or a Sequence in the single-form branch, is a genuine caller error these now catch explicitly instead of silently misbehaving. Verified against a from-scratch PETSc-enabled build: NEST vector assembly, block-offset vector assembly, single-form assembly, and both new guards' error paths. * Remove arg-type ignore in set_bc's NEST recursion The outer isinstance(bcs[0], Sequence) check establishes that bcs is genuinely 2D before reaching the NEST branch, but mypy can't propagate a check on an element (bcs[0]) to narrow bcs's own declared type. Add a per-iteration isinstance(bc, Sequence) guard instead, which mypy can use to narrow bc directly for the recursive set_bc call. Verified against a from-scratch PETSc-enabled build: NEST set_bc with correctly-nested bcs, and the new guard's error path with malformed input. * Remove 5 arg-type ignores in LinearProblem/assemble_residual block paths Two genuine pre-existing bugs surfaced once investigated: - LinearProblem.a/.preconditioner properties were typed Form | Sequence[Form], but __init__ actually accepts and stores Form | Sequence[Sequence[Form]] (a: ufl.Form | Sequence[Sequence[ ufl.Form]]), matching the class docstring's a_ij(u, v) block-matrix description. L is correctly 1D as declared. - extract_function_spaces's third @typing.overload declared -> list[list[FunctionSpace | None]] for 2D forms, but the implementation's 2D branch returns list(unique_spaces(V)) - a flat list, same shape as the second overload. Confirmed against the existing test_extract_function_spaces test, which indexes the result with Vc[0]/Vc[1], not Vc[0][0]. With both fixed, 3 call sites still needed a small isinstance guard (LinearProblem's block and single-form branches, assemble_residual's block branch), since self.a/self.L/residual are properties/parameters that can't be narrowed by checking a different variable (self.u/jacobian) - same idiom as the earlier set_bc/assemble_matrix fixes. Verified against a from-scratch PETSc-enabled build: a well-posed block LinearProblem.solve() (correct solution norms, exact Dirichlet BC enforcement), assemble_residual's block path, and the new guards' error paths. * Remove 16 type-ignore comments in fem/function.py 10 were stale - removing them produces no mypy error at all, in any of the three checked modes. The other 6 (in functionspace()) were caused by a real bug: the function reused its own element parameter (typed AbstractFiniteElement | ElementMetaData | tuple[...]) to hold the result of finiteelement(...), a completely different type (the compiled FiniteElement wrapper). Reassigning a parameter to an incompatible type confuses mypy's flow analysis for every subsequent use, not just the reassignment itself. Renamed to dolfinx_element. Two remaining ignores in the same function (689, 699) are left alone: a try/except TypeError duck-typing fallback that genuinely can't be narrowed without restructuring the ElementMetaData conversion. Verified: mypy clean in all three modes, ruff clean, test_function.py/test_custom_basix_element.py pass (74 tests), and a direct runtime check of functionspace() for both float32 and float64. * Remove 16 type-ignore comments in mesh.py/fem/element.py, fix h()'s hardcoded dtype 9 were stale - removing them produces no mypy error in any of the three checked modes. 4 more (coordinate_element's singledispatch base-vs-registered mismatch, same limitation as assemble_vector/assemble_matrix fixed earlier in petsc.py): named the anonymous registered variant _coordinate_element_from_basix and called it directly at all 4 call sites in mesh.py. 3 more, from two real bugs in refine()/uniform_refine(): - refine()'s return type omitted | None for parent_cell/parent_facet even though its own docstring says "(optional) parent cells, (optional) parent facets" and the underlying cpp function genuinely returns NDArray | None for both. - both functions accessed msh._ufl_domain.ufl_coordinate_element() without checking _ufl_domain (itself typed ufl.Mesh | None) isn't None first. Added explicit guards with a clear ValueError instead of a potential silent AttributeError. Also fixed (but did not remove the ignore for) Mesh.h()'s return type, hardcoded to npt.NDArray[np.float64] even though the underlying _cpp.mesh.h is genuinely overloaded per dtype and Mesh is Generic[Real] - changed to npt.NDArray[Real]. The ignore stays since _cpp_object's type still isn't tied to Real (same architectural gap as Geometry.x), but the annotation is now honest about float32 meshes. Verified: mypy clean in all three modes, ruff clean, 433 tests pass, and direct runtime checks of h() for both dtypes, normal refine/uniform_refine, both new guards' error paths, and both create_mesh code paths using the renamed function. * Remove 4 arg-type ignores in fem/utils.py via match-based narrowing create_interpolation_data, discrete_curl, discrete_gradient, and interpolate_geometry each take two or three independent FunctionSpace/ Mesh/Geometry/CoordinateElement/FiniteElement objects that must share a dtype for the underlying cpp overload to resolve - same shape as interpolation_matrix, fixed earlier with the same technique. Narrow via match/isinstance instead of ignoring, so a genuine dtype mismatch between the objects now raises a clear TypeError instead of an opaque nanobind overload failure. Verified: mypy clean in all three modes, ruff clean, 121 PETSc tests pass (test_petsc_discrete_operators.py, against a from-scratch PETSc-enabled build) plus 215 (test_interpolation.py) + 28 (test_interpolate_geometry.py) non-PETSc tests. * Remove 4 stale type-ignore comments in fem/bcs.py These 4 (the non-Iterable-V early-return branches and the block-form _V list comprehensions in locate_dofs_geometrical/locate_dofs_ topological) produce no mypy error at all once removed, in any of the three checked modes. The remaining 10 ignores in this file are genuine: DirichletBC.g/ function_space return the raw cpp object instead of the declared wrapped Function/Constant/FunctionSpace type (g even has its own "TODO: needs to be wrapped" comment - a known, deliberately-deferred gap, not something to silently implement here), and dirichletbc()'s _value/bctype correlation is intentional polymorphism across raw arrays, Function, Constant, and scalar values, not a variable-reuse bug. Verified: mypy clean in all three modes, ruff clean, test_bcs.py passes (24 tests), and a direct runtime check of both freed block-form call paths. * Remove more type: ignore comments in fem/forms.py - extract_function_spaces: remove stale union-attr ignore (forms is already narrowed at this point). - compile_form: replace assignment ignore with an explicit typing.cast, since ffcx.get_options() returns a heterogeneous dict. - derivative_block: extend the isinstance-based du/u narrowing already used in the rank-one branch to the rank-zero and block-Jacobian branches, removing the three remaining bare ignores. * Fix singledispatch call-arg bug in create_cell_partitioner call sites The @create_cell_partitioner.register(GhostMode) variant was anonymous (named _), so mypy checked its call sites against the 3-argument base function's signature instead of the actual 2-argument dispatched overload, producing a bogus "Missing positional argument" error at every call site. Name the registered function and call it directly at the two internal call sites (mesh.create_mesh, XDMFFile.read_mesh), removing both ignores. Also drop an inert '# F401' comment left over on the VTXMeshPolicy import (ruff confirms the import is used). * Fix same create_cell_partitioner call-arg bug in demo_mixed-topology.py Same root cause as the previous mesh.py/io/utils.py fix: call the named GhostMode-dispatch variant directly instead of through the generic singledispatch function, whose base signature mypy incorrectly checks call sites against. * Fix same create_cell_partitioner call-arg bug in demo_axis.py/demo_pml.py Same root cause and fix as the previous two commits: call the named GhostMode-dispatch variant directly instead of through the generic singledispatch function. * Remove stale type: ignore in plot.py Once the @overload/@singledispatch attr-defined error on vtk_mesh.register fires, mypy no longer independently checks the registered function body, making its own ignore comment redundant. * Replace var-annotated ignores with explicit type hints in demo_tnt-elements.py Empty-list literals can't have their element type inferred by mypy; annotate x/M as list[list[np.ndarray]] instead of suppressing. * Guard against None function space in NewtonSolver.__init__ extract_function_spaces(problem.L) is statically Optional; add an explicit None check before calling create_vector, which requires a non-optional FunctionSpace for its single-space overload. * Correct type: ignore error codes in VTXWriter for ADIOS2-enabled builds Verified against a real ADIOS2+petsc4py build: the previous ignore codes (attr-defined only, union-attr) were only correct for the no-ADIOS2 build and silently did nothing once ADIOS2 attributes genuinely exist. Add the assignment/arg-type codes that actually fire in an ADIOS2-enabled build, and add a missing ignore on the Function-sequence VTXWriter constructor call. * Remove stale type: ignore comments in la/petsc.py Verified against a real petsc4py build (by patching a locally-generated stub defect that was blocking mypy analysis entirely -- not part of this diff): PETSc is always a real, unconditionally-imported module in this file (guarded only by a runtime RuntimeError, never TYPE_CHECKING), so none of the name-defined/attr-defined ignores were ever needed. Only createGhostWithArray/createGhost's argument type mismatches and one singledispatch dispatch-type mismatch are genuine; corrected their codes and line placement to match where mypy actually reports them. * Correct type: ignore comments in nls/petsc.py Verified against a real petsc4py build: the A/b property ignores were stale (PETSc.Mat/Vec are real types here). solve/setP genuinely violate the Liskov substitution principle against the cpp base class's Vec/Mat signatures by design (the Python wrapper takes Function/high-level callables); give them the specific override code instead of a bare ignore. * Correct type: ignore comments in fem/petsc.py, fix singledispatch bugs Verified against a real petsc4py+ADIOS2 build (by patching a locally generated nanobind stub defect that was blocking mypy analysis entirely -- not part of this diff) and the no-petsc4py build: - 74 of 150 ignores were stale: PETSc.Vec/Mat/etc. are always real types here (petsc4py is unconditionally imported, never TYPE_CHECKING-gated), so the name-defined/attr-defined ignores from when this wasn't reliably checkable no longer apply. - 10 ignores had the wrong error code and were silently doing nothing. - 20 lines were missing ignores for errors that leaked through undetected. - Fixed 3 real call sites (LinearProblem.solve, assemble_jacobian, NewtonSolverNonlinearProblem.F) that called the generic assemble_matrix/assemble_vector singledispatch functions positionally plus a bcs= keyword, which mypy correctly flags as a keyword conflict against the singledispatch base signature (Python's singledispatch itself dispatches fine at runtime, but mypy only checks calls against the un-registered base signature). Call _assemble_matrix_petsc/_assemble_vector_petsc directly instead, matching the pattern already used elsewhere in this file. Verified with the full PETSc-marked pytest suite (174 passed) plus direct runtime smoke tests of assemble_matrix, assemble_vector, apply_lifting, LinearProblem.solve, and discrete_gradient. * Fix type: ignore comments in demo_stokes.py Verified against a real petsc4py build: numpy.dtype (PETSc.ScalarType's declared stub type) is a valid DTypeLike, so np.zeros(..., dtype=...) needed no ignore. Calling PETSc.ScalarType(0) as a constructor does genuinely error against that same stub type; give it the operator code. * Remove stale type: ignore comments in demo_matrix-free-petsc.py Verified against a real petsc4py build: these zip() unpackings type check cleanly once petsc4py's real stubs are available. * Fix type: ignore comments in demo_static-condensation.py Verified against a real petsc4py build: 12 of 15 ignores were stale. bc.set(b) was missing an ignore -- DirichletBC.set expects an ndarray, not the PETSc.Vec passed here. * Fix type: ignore comments for jv() calls in EM scattering demos Verified against a real petsc4py build: jv(nu, alpha) with a real alpha type-checks fine; only jv(nu, m * alpha), where m is complex, needed an ignore, with the call-overload code. * Fix type: ignore comments in several demos Verified against a real petsc4py build: the ScalarType import, PETSc.Sys() and PETSc.Error except-clause, and float32-check ignores were all stale. demo_pyamg.py's dirichletbc(value=dtype(0.0), ...) call genuinely errors against a runtime-constructed dtype; give it the specific operator/misc codes instead of a bare ignore. * Fix type: ignore comments in demo_axis.py Verified against a real petsc4py build: the complexfloating check was stale. sys = PETSc.Sys()/hasExternalPackage genuinely error against the petsc4py.PETSc module-vs-Sys-class stub; give them the specific assignment/attr-defined codes. * Remove stale type: ignore comments in demo_gmsh.py/demo_interpolation-io.py ignore_missing_imports = true is a global [tool.mypy] setting shared by every CI job's pyproject.toml, so an unstubbed import (gmsh) or an attribute access on an object derived from one (pyvista's Plotter) can never actually error under this config. * Fix singledispatch call-arg bug in fem/problems.py LinearProblem.solve Same root cause as the fem/petsc.py/mesh.py fixes: call the named MatrixCSR-dispatch variant (_assemble_matrix_csr) directly instead of through the generic singledispatch function, whose base signature mypy incorrectly checks call sites against. * Fix nanobind stub type names for PETSc Mat/Vec/IS/KSP casters PETSC_CASTER_MACRO used bare identifiers (mat, vec, is, ksp) as the nanobind stub type name, instead of fully-qualified petsc4py.PETSc.* names like caster_mpi.h correctly does for mpi4py.MPI.Comm. This produced invalid generated stubs everywhere these types appear (la.petsc, fem.petsc, nls.petsc) -- including a literal Python syntax error, since `is` is a keyword, that crashes mypy outright when checking against a real PETSc-enabled build's stubs. This is almost certainly why so much PETSc-touching code accumulated broad `# type: ignore` comments: mypy against these types was never reliably checkable to begin with. Verified by rebuilding the nanobind extension and regenerating stubs directly with nanobind.stubgen: la.petsc/fem.petsc/nls.petsc now emit valid, correctly-qualified types with the petsc4py.PETSc import auto-added. Full PETSc-marked pytest suite passes (174 tests). * Fix two more nanobind stub type leaks in la.cpp and io.h SparsityPattern's "concatenate sub-patterns" constructor took maps as a raw std::reference_wrapper<const IndexMap> directly in the nanobind- facing signature; nanobind has no caster that unwraps reference_wrapper to its underlying (already-bound) type, so the stub fell back to a raw, invalid C++ type-name string. Fixed following the pattern already used for DirichletBC elsewhere (assemble.h): accept shared_ptr<const IndexMap> at the binding boundary (which nanobind resolves natively) and build the reference_wrapper internally before forwarding to the real constructor. This was the sole cause of the dolfinx.cpp.la.__prefix__ valid-type suppression in stub_patterns.txt, confirmed by regenerating the stub and running mypy with the suppression removed -- now deleted. VTXWriter's Function-list constructor accepts all four scalar/geometry combinations at the C++ level (matching the real, intentional ADIOS2Writers.h API), but only the two matched-precision combinations per geometry type are ever bound to a Python fem.Function class, so the other two are Python-unreachable yet still leaked into the stub as unresolvable raw type names. Give the two per-T overloads an nb::sig override restricting the declared type to what's actually reachable; the C++ overload itself is unchanged. Verified by rebuilding, regenerating stubs directly via nanobind.stubgen, confirming mypy -p dolfinx is clean, and running the complete python/test suite (3108 passed, 92 skipped, 27 xfailed, matching the pre-change baseline). * Simplify nanobind stub generation in python/CMakeLists.txt Hoist install(TARGETS cpp ...) out of the ENABLE_NANOBIND_STUBGEN branches so the compiled module is always installed, even with stub generation disabled. Factor the duplicated 17-entry .pyi OUTPUT list into a single NANOBIND_STUB_OUTPUTS variable shared by the WIN32 and UNIX nanobind_add_stub() calls. * Always run nanobind stub generation, remove ENABLE_NANOBIND_STUBGEN option Stub generation is not opt-out in practice (no CI job or packaging path disables it), so the option only added an untested configuration path. Run it unconditionally instead. * Link nanobind_add_stub docs from the UNIX stub-staging comment Points readers at the mechanism (stubgen imports MODULE and infers output location from __file__) that motivates staging cpp under a throwaway dolfinx/ package directory. * Drop the symlink add_custom_command for UNIX stub staging Instead of building cpp normally and symlinking it into a throwaway dolfinx/ directory post-build, set the cpp target's LIBRARY_OUTPUT_DIRECTORY to build directly into that directory. install(TARGETS cpp ...) still locates the target correctly regardless of its output directory, so nothing else needs to change. Verified with a from-scratch build: cpp links directly into dolfinx/cpp.<ext>, stub generation produces the same dolfinx.cpp.<submodule>-style cross-references as before, and `cmake --install` places the .so under dolfinx/ as expected. * Use a regular (non-editable) install in the RHEL/Spack CI job The "AlmaLinux build and test" job has been failing deterministically on every run since the nanobind stub-generation work landed: every demo fails at import time with "ImportError: cannot import name 'cpp' from partially initialized module 'dolfinx' (most likely due to a circular import)". This job is the only CI job that installs dolfinx with `pip install -e` (editable). All non-editable installs across the rest of CI (the PETSc-enabled matrix in ccpp.yml, plus repeated local reproduction with an editable-install of this exact branch) succeed reliably. The new stub generation puts `dolfinx/cpp/*.pyi` (a directory of type stubs, matching nanobind's own convention for a compiled extension with nested submodules) directly alongside the compiled `dolfinx/cpp.<ext>` module. scikit-build-core's editable-install redirect builds a manifest that classifies each installed path as either a "wheel file" (compiled/source module) or a namespace-package search location; a `.pyi`-only directory that exactly shadows a compiled module's own name is an edge case scikit-build-core's own source comments show has caused prior classification bugs in this exact area (upstream issues FEniCS#1427, FEniCS#1482). This is a good fit for what we observe: the module resolves fine via the ordinary installed-path loader, but not through the editable redirect on this platform. This CI job doesn't need editable mode -- it builds once and immediately runs demos/tests against that one build, with no edit-and-rerun step in between -- so switching to a regular install sidesteps the redirect entirely rather than chasing the exact upstream classification bug. * Fix editable installs by requiring scikit-build-core>=1.0.0 Root-cause fix, replacing the earlier non-editable CI workaround (previous commit): editable installs were never actually broken by this PR's own code, but by a real bug in scikit-build-core <1.0.0's editable redirect finder. Empirically bisected locally (macOS, reproduced 100% on 0.11.0 through 0.12.2, 0/10 failures from 1.0.0 onward): the pre-1.0 redirect finder resolves a compiled module straight from its known file path via importlib.util.spec_from_file_location, without checking what else is on disk. nanobind's generated dolfinx/cpp/*.pyi stub directory (its standard convention for a compiled extension with nested submodules) sits right next to the compiled dolfinx/cpp.<ext> module, and the pre-1.0 build-time manifest scan registers dolfinx.cpp both as a "wheel file" (the .so) and, from the stub directory's __init__.pyi, as a package with its own search location -- confusing every subsequent `from dolfinx import cpp` in dolfinx/common.py. 1.0.0 resolves compiled modules through PathFinder instead, which correctly prefers the real file over the same-named stub directory regardless of the manifest ambiguity. Since this is a real upstream fix rather than a workaround, restore the RHEL/Spack CI job's editable install. That job's pinned Spack package repo only provides py-scikit-build-core up to 0.12.2 (confirmed by checking out the exact packages_ref tag), so pip-upgrade scikit-build-core to >=1.0.0 from PyPI specifically for that build step rather than relying on the Spack-provided one. * Fix two mypy ignores caused by name reuse across incompatible types demo_axis.py reused the module-level `sys` (the stdlib module, used for sys.argv) as a local PETSc.Sys() instance; mypy forbids narrowing a name to an incompatible type within the same scope. Renamed to petsc_sys, which needs no suppression at all. fem/assemble.py's _assemble_matrix_csr had the same pattern on the `bcs` parameter, reassigning it from Sequence[DirichletBC] | None to a list of raw _cpp_object handles. Renamed to _bcs (matching the existing convention in fem/petsc.py), which resolves the [misc] redefinition error. The [arg-type] ignore on the following _cpp.fem.assemble_matrix call stays -- confirmed via mypy that it suppresses three separate, genuine dtype-Union-vs-concrete-overload mismatches unrelated to the renaming. Verified with ruff check/format and a targeted mypy run against the built stubs: demo_axis.py now has zero errors, assemble.py's remaining ignore is the minimal one needed. * Fix genuinely-fixable mypy ignores in PETSc/scipy demos - demo_pyamg.py: narrow poisson_problem's dtype parameter from npt.DTypeLike to type[np.floating] | type[np.complexfloating], matching how it's actually called. This also exposed that dirichletbc's own value type hint was too narrow -- it already handles anything with a .dtype attribute at runtime, just didn't declare it -- so widen fem/bcs.py's dirichletbc signature to include raw numpy scalars instead of reaching for a lossy .item() conversion (which would have silently upcast float32 boundary values to float64). - demo_pml.py / demo_scattering-boundary-conditions.py: scipy-stubs does support complex arguments to jv, just typed as numpy.complex128 /complex64, not builtin complex -- wrap m * alpha accordingly. This uncovered a real bug: compute_a was annotated -> float but always returns a genuinely complex Mie coefficient (callers already take np.real/np.abs of it) -- fixed to -> complex in both files. - demo_mixed-topology.py: cast hexahedron/prism's _cpp_object to CoordinateElement_float64 (both are built with the default dtype=np.float64, so this matches runtime reality) instead of ignoring the dtype-Union mismatch. This gives create_mesh's return type real precision, which surfaced two more pre-existing errors further down the same file that were previously masked by the broken overload match; added targeted ignores for those (same wrapper-Union-vs-concrete-overload pattern as elsewhere, no clean local fix available). - assemble.py: insert_diagonal was still passed the stale `bcs` name after the earlier _bcs rename, a runtime bug (TypeError) hidden by a bare `# type: ignore`; fixed to reference _bcs, with the ignore narrowed to [call-overload] to match the actual error code. Verified with ruff check/format, mypy against the built stubs, and by actually running demo_pyamg.py (all four dtypes, correct precision preserved) and demo_mixed-topology.py (runs through everything touched here; its pre-existing failure further on, unrelated to this change, reproduces identically on unmodified main). * Clarify DirichletBC::set docs on ghost/owned-only x and x0 length Neither the Python nor the C++ doc comment previously explained why passing x with or without ghost entries changes what set() does. Traced the mechanism in DirichletBC.h's apply() lambda: _dofs0 always contains both owned and ghost dof indices, and the per-entry bounds check `_dofs0[i] < x.size()` is what makes an owned-only x safe (ghost indices are simply skipped) as well as a full local+ghost x (both get set). Also document that x0, when provided, must be at least as long as x -- only checked via assert in Debug/Developer builds, not a per-element bounds check like x itself. * Fix Group C: use PETSc.Vec.array_w instead of widening DirichletBC.set demo_static-condensation.py was the only demo passing a raw PETSc.Vec directly to DirichletBC.set(), which only works at runtime because nanobind's ndarray caster happens to accept anything satisfying the buffer protocol -- but statically needs an npt.NDArray, and PETSc.Vec isn't typed as satisfying that anywhere. Every other demo doing the identical assemble_vector/apply_lifting/ghostUpdate/set sequence (demo_elasticity.py, demo_stokes.py) already uses b.array_w for exactly this call; demo_static-condensation.py had just missed it. Widening fem/bcs.py's DirichletBC.set signature to accept a buffer-like type was considered and rejected: the underlying nanobind binding's own generated stub types x as a concrete ndarray[float64, ...] regardless, so a Python-level widening would only relocate the mismatch rather than resolve it, and would require either a hard petsc4py dependency (which fem/bcs.py deliberately avoids) or a buffer-protocol Protocol requiring Python's 3.12+ collections.abc.Buffer (project floor is 3.11). Verified with ruff check/format and mypy against the built stubs; b remains the same PETSc.Vec object afterward (array_w is a zero-copy view), so the later solver.solve(b, ...) call is unaffected. * Fix two real bugs found while reviewing bcs.py's type: ignore comments locate_dofs_geometrical/locate_dofs_topological's docstrings claimed that passing an iterable of function spaces returns "a 2-D array of shape (number of dofs, 2)". This is wrong: both the C++ implementation (std::array<std::vector<int32_t>, 2>) and every actual call site (test_bcs.py's dofs[0]/dofs[1] indexing) treat it as a list of one array per space. Fixed the docstrings, and split each function into @overload declarations so the return type (np.ndarray vs. list[np.ndarray]) is correctly narrowed per call site -- a plain Union return type was tried first and broke dofs= type-checking in 15 demos that pass a single FunctionSpace, since mypy can't tell from the Union alone which branch a given call site takes. That overload split then surfaced a second real bug: dirichletbc's `dofs` parameter was typed as a single ndarray only, but its C++ constructor also has a Sequence[ndarray]-accepting overload used when V is a sub-space and value's function space differs (e.g. demo_matrix-free-petsc.py, passing the dof-index pair straight from locate_dofs_topological((W.sub(0), V), ...)). Widened dofs to npt.NDArray[np.int32] | Sequence[npt.NDArray[np.int32]] and corrected the docstring accordingly. The remaining 5 ignores in this file (DirichletBC.set, and the dtype-dispatch construction in dirichletbc()) are the same wrapper-stores-a-dtype-Union-then-dispatches-at-runtime pattern seen throughout this codebase: bctype/`_value`/`self._cpp_object` are only known to be a *consistent* concrete dtype at runtime (via the cpp_types[dtype, geometry_dtype] lookup table), which mypy cannot verify statically. A typing.cast here would have to pick one of four concrete types with no static basis for which -- unlike the fixes above, there is no sound local fix without restructuring the dispatch mechanism itself, so these are left as targeted ignores. Verified with ruff check/format, mypy (-p dolfinx, test, and demo, all clean, matching CI's exact invocation), and pytest (test/unit/fem/test_bcs.py, 24/24 passing). * Fix two real bugs breaking CI: demo_mixed-topology.py and petsc.py contains() demo_mixed-topology.py crashed on every CI run (confirmed identical on unmodified main via git stash, so unrelated to this PR's own commits): `dirichletbc(value=0.0, dofs=bcdofs, V=V_cpp)` passed a raw C++ FunctionSpace built from a raw C++ Mesh (both from the low-level dolfinx.cpp.mesh.create_mesh binding this demo uses directly, since UFL doesn't yet support mixed-topology domains). dirichletbc needs V.mesh to have a real UFL domain to build the Constant for the boundary value, but a raw cpp Mesh has no ufl_domain()/_ufl_is_terminal_. Fixed by reusing the same Mesh(mesh, domain)/FunctionSpace(...) wrapping idiom the file already uses later (line ~186) for form assembly -- picking one cell type's domain/element arbitrarily, since neither is used for anything beyond this association. fem/petsc.py's _assemble_matrix_petsc called `row_forms[0].function_spaces[0].contains(bc.function_space)`, but `.contains()`'s only overload takes a raw cpp FunctionSpace while `bc.function_space` returns the Python wrapper -- a TypeError on every block-assembled LinearProblem.solve() with a DirichletBC, breaking demo_stokes.py's nested_iterative_solver_high_level and demo_mixed-poisson.py in the PETSc-enabled CI matrix. Fixed by passing bc.function_space._cpp_object instead. (This fix already existed uncommitted in the worktree from earlier work -- committing it now since it's exactly what these two failing demos need.) Verified demo_mixed-topology.py runs to completion locally (prints "Solution vector norm ...", no exceptions) plus a clean mypy/ruff pass. petsc.py's fix verified against the exact CI traceback (same file, same line, same call site in both demo_stokes.py and demo_mixed-poisson.py); could not run it directly in this session's non-PETSc local build, but the fix is unambiguous: .contains()'s sole registered overload requires a raw cpp FunctionSpace_float64, which ._cpp_object provides and the bare wrapper does not. --------- Co-authored-by: qbisicwate <qbisicwate@gmail.com> Co-authored-by: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Co-authored-by: Jack S. Hale <mail@jackhale.co.uk> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…S#4348) * add cpp stub files (#4096) * add cpp stub files * fix nanobind OUTPUT path Note that these OUTPUT are not actually passed to the stub generator and purely used for dependency management within CMake. * Fix: reserved python global keyword * Fix: non-install time for UNIX platforms + CI adaptation * Fix: scoped log import * fix: petsc... * Work in progress on autogenerating nanobind stubs * Generate dolfinx.cpp stubs automatically * Add a way to disable nanobind stubgen (e.g. HPC builds) * Fix formatting * Fix on platforms without petsc4py * Use spack-fenics due to updated base deps * Back to main * Fix. * Revert * Fix Windows module name * Fix mypy failures surfaced by nanobind stub generation The auto-generated dolfinx.cpp stubs made mypy see real, precise types for the compiled extension for the first time, surfacing ~446 errors in the "Build and test" CI job (the only mypy invocation that actually installs dolfinx before running mypy, so the only one exercising the generated stubs). Root causes and fixes: - Unix stub generation imported the compiled module as bare `cpp` instead of `dolfinx.cpp`, so nanobind wrote cross-submodule references like `import cpp.la`, which doesn't exist and silently resolved to Any under mypy, masking real errors and producing bogus "overload can never match" diagnostics. Stage the built module under a throwaway dolfinx/ package dir before invoking nanobind_add_stub so the module's real __name__ is dolfinx.cpp. - `_IntegralType`/`MPICommWrapper` bindings used names or const_name() values with no resolvable Python type, breaking stub cross-refs. - `FiniteElement`/`AdjacencyList` `__eq__` bindings exposed the raw C++ operator==, violating object.__eq__'s Liskov contract; wrapped in a lambda with an isinstance guard instead. - Dead, unreachable overloads (complex instantiations of interpolation_matrix/discrete_curl/discrete_gradient that are always shadowed by the real ones, and a redundant read_geometry_data registration) removed. - Missing nanobind/stl/map.h and .../string.h includes were causing stubgen to fall back to invalid raw C++ type strings. - ~250 Python-side errors are the same runtime-safe-but-statically- unverifiable scalar-type dispatch pattern already handled elsewhere in this PR (fem/petsc.py); fixed with matching `# type: ignore`. A handful were real bugs instead: wrong return-type annotations in forms.py, and dispatch-table locals missing an explicit Union annotation. - The ~24 remaining errors are genuine ecosystem-level gaps confirmed via isolated repros (mypy can't disambiguate numpy dtype/rank in NDArray annotations; nanobind has no std::reference_wrapper caster; basix's C++ types aren't stub-resolvable across the package boundary) rather than dolfinx bugs. Suppressed per-file via a new nanobind stub pattern file (stub_patterns.txt) that injects a scoped `# mypy: disable-error-code=...` into just the affected generated stubs. Verified: mypy clean (matching the failing job's exact PETSc/ADIOS2 config), ruff/clang-format clean, and a second full build with PETSc+SLEPc+ADIOS2+ParMETIS+SuperLU_DIST (-Werror) compiles cleanly with runtime sanity checks passing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Extend mypy type-ignore fixes for new/changed main-branch APIs Post-merge fixups: main added a mesh argument to pack_coefficients, an interpolate_geometry function, and reshaped a few overload call sites (dofmaps as a sequence instead of a method, apply_lifting's now-arg-type instead of call-overload mismatch). Same runtime-safe/ statically-unverifiable scalar-dispatch pattern as the rest of this branch; verified mypy clean again afterwards. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Format python/CMakeLists.txt with gersemi Fixes Lint CI failure introduced by the main merge, which brought in the gersemi CMake formatter (replacing cmake-format). Purely cosmetic reformatting of the nanobind stub-generation additions; no logic changes. * Fix mypy errors only visible in package-mode type checking The "Build and test" CI job runs mypy in package mode (`mypy -p dolfinx`) against a genuinely installed dolfinx with real nanobind-generated stubs, unlike the Lint job's `mypy dolfinx`, which never builds/installs dolfinx and so resolves dolfinx.cpp.* as Any via ignore_missing_imports, masking overload-resolution errors entirely. Package mode surfaced 34 real errors invisible in Lint mode: - bcs.py/assemble.py: the DirichletBC/insert_diagonal overload mismatches report as call-overload in package mode, not the arg-type code the ignores were scoped to; switch to codeless ignores since the reported code is unstable across the two modes. - io/utils.py: new arg-type error on write_function's getattr cast. - mesh.py: refine()'s partitioner annotation didn't include None, even though the docstring documents None as a valid value and callers (test_refinement.py) pass it. - test_mesh_partitioners.py: overly narrow inferred list element type broke when a ParameterSet skip-marker was appended. - test_mesh.py: partitioner_kahip/partitioner_parmetis are only present on the compiled module when built with those optional backends, which mypy can't know statically. - demo_mixed-topology.py, demo_static-condensation.py: same dtype-union-overload ambiguity pattern fixed elsewhere in this PR. Verified via a from-scratch venv replicating the CI job exactly (Python 3.12, non-editable install, real generated stubs): mypy passes in all three invocation modes (Lint's mypy dolfinx/test/demo, and package-mode -p dolfinx/test/demo), ruff check/format clean, and the affected test files pass under pytest. * Also ignore partitioner_scotch attr-defined in test_mesh.py The CI runner for the "Build and test" job doesn't have libscotch installed, so partitioner_scotch is absent from the compiled module there too (unlike my local reproduction, which has SCOTCH via Homebrew) -- same build-config-dependent attribute-existence issue as partitioner_kahip/partitioner_parmetis fixed in the previous commit. * Remove two avoidable mypy ignores - fem/utils.py: narrow space0/space1's cpp objects via a match statement in interpolation_matrix so mypy can resolve the matching interpolation_matrix overload directly, instead of ignoring the call. Also gives callers a clear TypeError on mismatched dtypes instead of an opaque nanobind overload-resolution failure. - io/gmsh.py: read_from_msh's partitioner parameter typed its callback's 4th argument as AdjacencyList (the pure-Python wrapper, unused elsewhere in this file) instead of _AdjacencyList_int32 (the cpp type that model_to_mesh, which it delegates to, actually expects). Fixing the annotation removes the genuine type mismatch instead of suppressing it. * Switch VTKFile/XDMFFile to composition instead of subclassing cpp types VTKFile and XDMFFile subclassed their nanobind-bound counterparts directly (_cpp.io.VTKFile/_cpp.io.XDMFFile), unlike Mesh, Function, FunctionSpace, and VTXWriter, which all wrap their cpp object via a _cpp_object attribute instead. Subclassing meant every overridden method that re-types its arguments from the raw cpp type to the friendlier Python wrapper type (Mesh vs Mesh_float32/64, etc.) was a genuine Liskov substitution violation from mypy's point of view, requiring a # type: ignore[override] on write_mesh, write_meshtags, write_function, and read_meshtags. Switching to composition removes the inheritance relationship, so these methods are no longer checked against a base signature and the four ignores are gone with no suppression left behind. This requires explicit delegation for every previously-inherited method actually used elsewhere (close, comm, flush, write_information, read_information, read_topology_data, read_geometry_data, read_cell_type), confirmed via a grep across tests and demos, plus write_geometry for API parity even though nothing in-tree calls it. read_meshtags's and the new write_geometry's underlying cpp functions only support float64 meshes/geometry; narrowed via isinstance instead of suppressing, giving a real static check and a clear TypeError for the unsupported case instead of an ignore. * Remove 3 more avoidable mypy ignores in io/vtkhdf.py - read_mesh's filename was passed straight through as str | Path, but the cpp read_vtkhdf_mesh_float32/64 bindings only accept str; cast explicitly instead of ignoring, narrowing the float32 branch's ignore down to just the pre-existing, unrelated assignment error. - variant (mesh_cpp.geometry.cmaps[0].variant) is a raw int from the cpp binding; basix.ufl.element expects a LagrangeVariant. Wrap it, matching the same conversion already used in fem/utils.py's interpolate_geometry. - cell_types[0].name's ignore was stale: removing it produces no error, confirmed by rebuilding and rerunning mypy. Found via a full audit: stripped every remaining arg-type ignore in the tree at once, rebuilt against a from-scratch venv mirroring CI's package-mode mypy check, and inspected every resulting error. Outside of this file, everything else needs its ignore back - each is a single or multiple _cpp_object-typed argument whose Python-level type is a plain dtype union unconnected to any generic parameter (same pattern as FunctionSpace/Mesh/Form discussed elsewhere), so narrowing would mean adding an isinstance/match branch per dtype for no real safety benefit. Left those as ignores rather than trade a suppression for genuine complexity. * Remove remaining ignore[assignment] in vtkhdf.read_mesh mesh_cpp's type was inferred from its first assignment (read_vtkhdf_mesh_float64 -> Mesh_float64), so the second branch's Mesh_float32 assignment was flagged as incompatible. Declare the union type explicitly up front, matching the same pattern already used for the analogous float32/float64 dispatch in mesh.py's create_interval/create_rectangle/create_box. * Remove 5 stale arg-type ignores Found via a systematic strip-and-rebuild audit of every remaining ignore[arg-type] comment: these 5 produce no mypy error at all once removed, in package mode, mypy test, and mypy demo. No code changes beyond deleting the comments. * Fix wrong partitioner Callable signature in gmsh.py model_to_mesh's and read_from_msh's partitioner parameter was typed as Callable[[Comm, int, int, AdjacencyList_int32], AdjacencyList_int32], but that value is passed straight into create_mesh, whose cpp binding requires Callable[[Comm, int, Sequence[CellType], Sequence[NDArray[int64]]], AdjacencyList_int32] - matching what dolfinx.mesh.create_cell_partitioner's return type already documents. The 3rd/4th argument types were simply wrong, not an inherent dtype-overload ambiguity. * Remove 2 arg-type ignores in geometry.bb_tree Branch on isinstance(mesh._cpp_object, Mesh_float32/64) instead of np.issubdtype(mesh.geometry.x.dtype, ...) - same if/elif structure, but discriminating on the actual value being passed to the cpp constructor lets mypy narrow it through each branch. * Remove arg-type ignore in mesh.create_geometry The function already validates that element.dtype matches x.dtype before construction, so element._cpp_object's own concrete type already determines which Geometry class to build - dispatch off isinstance(cpp_element, ...) directly instead of a separate ftype lookup keyed on x.dtype. Removes a layer of indirection rather than adding one. Incidental fix: the "Unknown floating type for geometry" message was missing its f-string prefix, so {x.dtype} was never interpolated. * Remove 2 arg-type ignores in mesh.create_point_mesh geometry was just constructed from points.dtype a few lines above, so geometry._cpp_object's own concrete type already determines which Mesh class to build; branch on isinstance(cpp_geometry, ...) instead of points.dtype == ... for the same reason as the bb_tree/ create_geometry fixes. * Remove arg-type ignore in mesh.create_cell_partitioner @singledispatch requires the base function's part parameter to be typed Callable | GhostMode (a supertype of the registered GhostMode variant), but the base implementation is only ever reached when part is genuinely a Callable - a GhostMode argument gets routed to the registered variant instead. Add a defensive isinstance guard so mypy narrows part to Callable for the rest of the function, matching what was already true at runtime. * Remove 2 arg-type ignores in graph.comm_graph_data/comm_to_json Both are non-overloaded cpp functions accepting only AdjacencyList_int_sizet_int8__int32_int32, and the only Python-level producer of that type is comm_graph() (confirmed via the cpp stub: its return type is unconditionally that one class). Add an isinstance guard, giving callers a clear error instead of an opaque nanobind overload failure if they pass an AdjacencyList from anywhere else. * Remove arg-type ignore in LinearProblem's preconditioner form form()'s parameter was typed ufl.Form | Sequence[ufl.Form] | Sequence[Sequence[ufl.Form]] with no None, even though LinearProblem's P (the preconditioner) is documented and typed as optional, and _create_form's implementation already passes None straight through unchanged via its final `else: return form` branch. Widen the annotation to match what the implementation already does. * Remove arg-type ignore in derivative_block's rank-one Jacobian branch The rank-zero branch already guards u with isinstance(u, Function) / isinstance(u, Sequence), raising a clear ValueError otherwise. The rank-one branch called _derive_univariate_jacobian(F, u, du) (which requires u: Function, du: ufl.Argument | None) with no equivalent guard, so a caller passing u or du as a sequence would previously fall through silently instead of getting the same clear error the sibling branch gives. * Remove arg-type ignore in LinearProblem.solve mypy type-checks functools.singledispatch calls only against the base function's signature (L: typing.Any, constants: npt.NDArray | None, ...), so passing self.L (a Form) as the second positional argument to assemble_vector(self.b.array, self.L) was checked against constants: npt.NDArray | None and flagged. self.b.array/self.L are concretely npt.NDArray/Form here (not ambiguous unions), so calling the registered variant _assemble_vector_array directly - the exact function singledispatch would have dispatched to anyway - resolves cleanly. Same pattern already used in fem/petsc.py for the same functools.singledispatch/mypy limitation. * Remove 6 arg-type ignores in fem/petsc.py assemble_vector/assemble_matrix functools.singledispatch base functions recursively called their own singledispatch wrapper to reach the registered PETSc.Vec/PETSc.Mat variant, but mypy only checks singledispatch calls against the base signature - which is shaped for the "no b/A supplied yet" case, not the (b, L, ...)/(A, a, ...) shape actually being passed. Name the two previously-anonymous registered variants (_assemble_vector_petsc, _assemble_matrix_petsc) and call them directly at each of the 6 call sites (the base function's own recursive call, the NEST sub-block recursion, and 4 call sites in LinearProblem/assemble_residual/ NewtonSolverNonlinearProblem), bypassing the mismatched base check entirely. Also fixes a genuine pre-existing bug: the base assemble_matrix's constants/coeffs parameter types didn't match its own a: Form | Sequence[Sequence[Form]] shape (Sequence[X]/Sequence[dict] instead of bare X/Sequence[Sequence[dict]]), inconsistent with the correctly-typed registered sibling - this was the source of 2 of the 6 errors surviving the rename alone. Adds one small isinstance(coeffs, dict) guard in the NEST-matrix branch, mirroring the existing isinstance(a, Sequence) check right above it, for a genuinely-reachable misuse case. Verified against a from-scratch PETSc-enabled build of this worktree (petsc4py isn't available in the mypy-checking venv used elsewhere in this branch's history): assemble_vector/assemble_matrix in both their new-object and existing-object forms, LinearProblem.solve(), assemble_residual, NewtonSolverNonlinearProblem.J, NEST-matrix assembly, and the new guard's error path. * Remove 3 more arg-type ignores in fem/petsc.py assemble_vector Same root cause as assemble_matrix's NEST branch: constants/coeffs still carry their full declared union type (including shapes meant for the other branches) at each _assemble_vector_array call site, since narrowing L doesn't narrow the separate constants/coeffs parameters. Add isinstance guards mirroring the existing isinstance(L, Sequence) checks - a bare dict in the NEST/block branches, or a Sequence in the single-form branch, is a genuine caller error these now catch explicitly instead of silently misbehaving. Verified against a from-scratch PETSc-enabled build: NEST vector assembly, block-offset vector assembly, single-form assembly, and both new guards' error paths. * Remove arg-type ignore in set_bc's NEST recursion The outer isinstance(bcs[0], Sequence) check establishes that bcs is genuinely 2D before reaching the NEST branch, but mypy can't propagate a check on an element (bcs[0]) to narrow bcs's own declared type. Add a per-iteration isinstance(bc, Sequence) guard instead, which mypy can use to narrow bc directly for the recursive set_bc call. Verified against a from-scratch PETSc-enabled build: NEST set_bc with correctly-nested bcs, and the new guard's error path with malformed input. * Remove 5 arg-type ignores in LinearProblem/assemble_residual block paths Two genuine pre-existing bugs surfaced once investigated: - LinearProblem.a/.preconditioner properties were typed Form | Sequence[Form], but __init__ actually accepts and stores Form | Sequence[Sequence[Form]] (a: ufl.Form | Sequence[Sequence[ ufl.Form]]), matching the class docstring's a_ij(u, v) block-matrix description. L is correctly 1D as declared. - extract_function_spaces's third @typing.overload declared -> list[list[FunctionSpace | None]] for 2D forms, but the implementation's 2D branch returns list(unique_spaces(V)) - a flat list, same shape as the second overload. Confirmed against the existing test_extract_function_spaces test, which indexes the result with Vc[0]/Vc[1], not Vc[0][0]. With both fixed, 3 call sites still needed a small isinstance guard (LinearProblem's block and single-form branches, assemble_residual's block branch), since self.a/self.L/residual are properties/parameters that can't be narrowed by checking a different variable (self.u/jacobian) - same idiom as the earlier set_bc/assemble_matrix fixes. Verified against a from-scratch PETSc-enabled build: a well-posed block LinearProblem.solve() (correct solution norms, exact Dirichlet BC enforcement), assemble_residual's block path, and the new guards' error paths. * Remove 16 type-ignore comments in fem/function.py 10 were stale - removing them produces no mypy error at all, in any of the three checked modes. The other 6 (in functionspace()) were caused by a real bug: the function reused its own element parameter (typed AbstractFiniteElement | ElementMetaData | tuple[...]) to hold the result of finiteelement(...), a completely different type (the compiled FiniteElement wrapper). Reassigning a parameter to an incompatible type confuses mypy's flow analysis for every subsequent use, not just the reassignment itself. Renamed to dolfinx_element. Two remaining ignores in the same function (689, 699) are left alone: a try/except TypeError duck-typing fallback that genuinely can't be narrowed without restructuring the ElementMetaData conversion. Verified: mypy clean in all three modes, ruff clean, test_function.py/test_custom_basix_element.py pass (74 tests), and a direct runtime check of functionspace() for both float32 and float64. * Remove 16 type-ignore comments in mesh.py/fem/element.py, fix h()'s hardcoded dtype 9 were stale - removing them produces no mypy error in any of the three checked modes. 4 more (coordinate_element's singledispatch base-vs-registered mismatch, same limitation as assemble_vector/assemble_matrix fixed earlier in petsc.py): named the anonymous registered variant _coordinate_element_from_basix and called it directly at all 4 call sites in mesh.py. 3 more, from two real bugs in refine()/uniform_refine(): - refine()'s return type omitted | None for parent_cell/parent_facet even though its own docstring says "(optional) parent cells, (optional) parent facets" and the underlying cpp function genuinely returns NDArray | None for both. - both functions accessed msh._ufl_domain.ufl_coordinate_element() without checking _ufl_domain (itself typed ufl.Mesh | None) isn't None first. Added explicit guards with a clear ValueError instead of a potential silent AttributeError. Also fixed (but did not remove the ignore for) Mesh.h()'s return type, hardcoded to npt.NDArray[np.float64] even though the underlying _cpp.mesh.h is genuinely overloaded per dtype and Mesh is Generic[Real] - changed to npt.NDArray[Real]. The ignore stays since _cpp_object's type still isn't tied to Real (same architectural gap as Geometry.x), but the annotation is now honest about float32 meshes. Verified: mypy clean in all three modes, ruff clean, 433 tests pass, and direct runtime checks of h() for both dtypes, normal refine/uniform_refine, both new guards' error paths, and both create_mesh code paths using the renamed function. * Remove 4 arg-type ignores in fem/utils.py via match-based narrowing create_interpolation_data, discrete_curl, discrete_gradient, and interpolate_geometry each take two or three independent FunctionSpace/ Mesh/Geometry/CoordinateElement/FiniteElement objects that must share a dtype for the underlying cpp overload to resolve - same shape as interpolation_matrix, fixed earlier with the same technique. Narrow via match/isinstance instead of ignoring, so a genuine dtype mismatch between the objects now raises a clear TypeError instead of an opaque nanobind overload failure. Verified: mypy clean in all three modes, ruff clean, 121 PETSc tests pass (test_petsc_discrete_operators.py, against a from-scratch PETSc-enabled build) plus 215 (test_interpolation.py) + 28 (test_interpolate_geometry.py) non-PETSc tests. * Remove 4 stale type-ignore comments in fem/bcs.py These 4 (the non-Iterable-V early-return branches and the block-form _V list comprehensions in locate_dofs_geometrical/locate_dofs_ topological) produce no mypy error at all once removed, in any of the three checked modes. The remaining 10 ignores in this file are genuine: DirichletBC.g/ function_space return the raw cpp object instead of the declared wrapped Function/Constant/FunctionSpace type (g even has its own "TODO: needs to be wrapped" comment - a known, deliberately-deferred gap, not something to silently implement here), and dirichletbc()'s _value/bctype correlation is intentional polymorphism across raw arrays, Function, Constant, and scalar values, not a variable-reuse bug. Verified: mypy clean in all three modes, ruff clean, test_bcs.py passes (24 tests), and a direct runtime check of both freed block-form call paths. * Remove more type: ignore comments in fem/forms.py - extract_function_spaces: remove stale union-attr ignore (forms is already narrowed at this point). - compile_form: replace assignment ignore with an explicit typing.cast, since ffcx.get_options() returns a heterogeneous dict. - derivative_block: extend the isinstance-based du/u narrowing already used in the rank-one branch to the rank-zero and block-Jacobian branches, removing the three remaining bare ignores. * Fix singledispatch call-arg bug in create_cell_partitioner call sites The @create_cell_partitioner.register(GhostMode) variant was anonymous (named _), so mypy checked its call sites against the 3-argument base function's signature instead of the actual 2-argument dispatched overload, producing a bogus "Missing positional argument" error at every call site. Name the registered function and call it directly at the two internal call sites (mesh.create_mesh, XDMFFile.read_mesh), removing both ignores. Also drop an inert '# F401' comment left over on the VTXMeshPolicy import (ruff confirms the import is used). * Fix same create_cell_partitioner call-arg bug in demo_mixed-topology.py Same root cause as the previous mesh.py/io/utils.py fix: call the named GhostMode-dispatch variant directly instead of through the generic singledispatch function, whose base signature mypy incorrectly checks call sites against. * Fix same create_cell_partitioner call-arg bug in demo_axis.py/demo_pml.py Same root cause and fix as the previous two commits: call the named GhostMode-dispatch variant directly instead of through the generic singledispatch function. * Remove stale type: ignore in plot.py Once the @overload/@singledispatch attr-defined error on vtk_mesh.register fires, mypy no longer independently checks the registered function body, making its own ignore comment redundant. * Replace var-annotated ignores with explicit type hints in demo_tnt-elements.py Empty-list literals can't have their element type inferred by mypy; annotate x/M as list[list[np.ndarray]] instead of suppressing. * Guard against None function space in NewtonSolver.__init__ extract_function_spaces(problem.L) is statically Optional; add an explicit None check before calling create_vector, which requires a non-optional FunctionSpace for its single-space overload. * Correct type: ignore error codes in VTXWriter for ADIOS2-enabled builds Verified against a real ADIOS2+petsc4py build: the previous ignore codes (attr-defined only, union-attr) were only correct for the no-ADIOS2 build and silently did nothing once ADIOS2 attributes genuinely exist. Add the assignment/arg-type codes that actually fire in an ADIOS2-enabled build, and add a missing ignore on the Function-sequence VTXWriter constructor call. * Remove stale type: ignore comments in la/petsc.py Verified against a real petsc4py build (by patching a locally-generated stub defect that was blocking mypy analysis entirely -- not part of this diff): PETSc is always a real, unconditionally-imported module in this file (guarded only by a runtime RuntimeError, never TYPE_CHECKING), so none of the name-defined/attr-defined ignores were ever needed. Only createGhostWithArray/createGhost's argument type mismatches and one singledispatch dispatch-type mismatch are genuine; corrected their codes and line placement to match where mypy actually reports them. * Correct type: ignore comments in nls/petsc.py Verified against a real petsc4py build: the A/b property ignores were stale (PETSc.Mat/Vec are real types here). solve/setP genuinely violate the Liskov substitution principle against the cpp base class's Vec/Mat signatures by design (the Python wrapper takes Function/high-level callables); give them the specific override code instead of a bare ignore. * Correct type: ignore comments in fem/petsc.py, fix singledispatch bugs Verified against a real petsc4py+ADIOS2 build (by patching a locally generated nanobind stub defect that was blocking mypy analysis entirely -- not part of this diff) and the no-petsc4py build: - 74 of 150 ignores were stale: PETSc.Vec/Mat/etc. are always real types here (petsc4py is unconditionally imported, never TYPE_CHECKING-gated), so the name-defined/attr-defined ignores from when this wasn't reliably checkable no longer apply. - 10 ignores had the wrong error code and were silently doing nothing. - 20 lines were missing ignores for errors that leaked through undetected. - Fixed 3 real call sites (LinearProblem.solve, assemble_jacobian, NewtonSolverNonlinearProblem.F) that called the generic assemble_matrix/assemble_vector singledispatch functions positionally plus a bcs= keyword, which mypy correctly flags as a keyword conflict against the singledispatch base signature (Python's singledispatch itself dispatches fine at runtime, but mypy only checks calls against the un-registered base signature). Call _assemble_matrix_petsc/_assemble_vector_petsc directly instead, matching the pattern already used elsewhere in this file. Verified with the full PETSc-marked pytest suite (174 passed) plus direct runtime smoke tests of assemble_matrix, assemble_vector, apply_lifting, LinearProblem.solve, and discrete_gradient. * Fix type: ignore comments in demo_stokes.py Verified against a real petsc4py build: numpy.dtype (PETSc.ScalarType's declared stub type) is a valid DTypeLike, so np.zeros(..., dtype=...) needed no ignore. Calling PETSc.ScalarType(0) as a constructor does genuinely error against that same stub type; give it the operator code. * Remove stale type: ignore comments in demo_matrix-free-petsc.py Verified against a real petsc4py build: these zip() unpackings type check cleanly once petsc4py's real stubs are available. * Fix type: ignore comments in demo_static-condensation.py Verified against a real petsc4py build: 12 of 15 ignores were stale. bc.set(b) was missing an ignore -- DirichletBC.set expects an ndarray, not the PETSc.Vec passed here. * Fix type: ignore comments for jv() calls in EM scattering demos Verified against a real petsc4py build: jv(nu, alpha) with a real alpha type-checks fine; only jv(nu, m * alpha), where m is complex, needed an ignore, with the call-overload code. * Fix type: ignore comments in several demos Verified against a real petsc4py build: the ScalarType import, PETSc.Sys() and PETSc.Error except-clause, and float32-check ignores were all stale. demo_pyamg.py's dirichletbc(value=dtype(0.0), ...) call genuinely errors against a runtime-constructed dtype; give it the specific operator/misc codes instead of a bare ignore. * Fix type: ignore comments in demo_axis.py Verified against a real petsc4py build: the complexfloating check was stale. sys = PETSc.Sys()/hasExternalPackage genuinely error against the petsc4py.PETSc module-vs-Sys-class stub; give them the specific assignment/attr-defined codes. * Remove stale type: ignore comments in demo_gmsh.py/demo_interpolation-io.py ignore_missing_imports = true is a global [tool.mypy] setting shared by every CI job's pyproject.toml, so an unstubbed import (gmsh) or an attribute access on an object derived from one (pyvista's Plotter) can never actually error under this config. * Fix singledispatch call-arg bug in fem/problems.py LinearProblem.solve Same root cause as the fem/petsc.py/mesh.py fixes: call the named MatrixCSR-dispatch variant (_assemble_matrix_csr) directly instead of through the generic singledispatch function, whose base signature mypy incorrectly checks call sites against. * Fix nanobind stub type names for PETSc Mat/Vec/IS/KSP casters PETSC_CASTER_MACRO used bare identifiers (mat, vec, is, ksp) as the nanobind stub type name, instead of fully-qualified petsc4py.PETSc.* names like caster_mpi.h correctly does for mpi4py.MPI.Comm. This produced invalid generated stubs everywhere these types appear (la.petsc, fem.petsc, nls.petsc) -- including a literal Python syntax error, since `is` is a keyword, that crashes mypy outright when checking against a real PETSc-enabled build's stubs. This is almost certainly why so much PETSc-touching code accumulated broad `# type: ignore` comments: mypy against these types was never reliably checkable to begin with. Verified by rebuilding the nanobind extension and regenerating stubs directly with nanobind.stubgen: la.petsc/fem.petsc/nls.petsc now emit valid, correctly-qualified types with the petsc4py.PETSc import auto-added. Full PETSc-marked pytest suite passes (174 tests). * Fix two more nanobind stub type leaks in la.cpp and io.h SparsityPattern's "concatenate sub-patterns" constructor took maps as a raw std::reference_wrapper<const IndexMap> directly in the nanobind- facing signature; nanobind has no caster that unwraps reference_wrapper to its underlying (already-bound) type, so the stub fell back to a raw, invalid C++ type-name string. Fixed following the pattern already used for DirichletBC elsewhere (assemble.h): accept shared_ptr<const IndexMap> at the binding boundary (which nanobind resolves natively) and build the reference_wrapper internally before forwarding to the real constructor. This was the sole cause of the dolfinx.cpp.la.__prefix__ valid-type suppression in stub_patterns.txt, confirmed by regenerating the stub and running mypy with the suppression removed -- now deleted. VTXWriter's Function-list constructor accepts all four scalar/geometry combinations at the C++ level (matching the real, intentional ADIOS2Writers.h API), but only the two matched-precision combinations per geometry type are ever bound to a Python fem.Function class, so the other two are Python-unreachable yet still leaked into the stub as unresolvable raw type names. Give the two per-T overloads an nb::sig override restricting the declared type to what's actually reachable; the C++ overload itself is unchanged. Verified by rebuilding, regenerating stubs directly via nanobind.stubgen, confirming mypy -p dolfinx is clean, and running the complete python/test suite (3108 passed, 92 skipped, 27 xfailed, matching the pre-change baseline). * Simplify nanobind stub generation in python/CMakeLists.txt Hoist install(TARGETS cpp ...) out of the ENABLE_NANOBIND_STUBGEN branches so the compiled module is always installed, even with stub generation disabled. Factor the duplicated 17-entry .pyi OUTPUT list into a single NANOBIND_STUB_OUTPUTS variable shared by the WIN32 and UNIX nanobind_add_stub() calls. * Always run nanobind stub generation, remove ENABLE_NANOBIND_STUBGEN option Stub generation is not opt-out in practice (no CI job or packaging path disables it), so the option only added an untested configuration path. Run it unconditionally instead. * Link nanobind_add_stub docs from the UNIX stub-staging comment Points readers at the mechanism (stubgen imports MODULE and infers output location from __file__) that motivates staging cpp under a throwaway dolfinx/ package directory. * Drop the symlink add_custom_command for UNIX stub staging Instead of building cpp normally and symlinking it into a throwaway dolfinx/ directory post-build, set the cpp target's LIBRARY_OUTPUT_DIRECTORY to build directly into that directory. install(TARGETS cpp ...) still locates the target correctly regardless of its output directory, so nothing else needs to change. Verified with a from-scratch build: cpp links directly into dolfinx/cpp.<ext>, stub generation produces the same dolfinx.cpp.<submodule>-style cross-references as before, and `cmake --install` places the .so under dolfinx/ as expected. * Use a regular (non-editable) install in the RHEL/Spack CI job The "AlmaLinux build and test" job has been failing deterministically on every run since the nanobind stub-generation work landed: every demo fails at import time with "ImportError: cannot import name 'cpp' from partially initialized module 'dolfinx' (most likely due to a circular import)". This job is the only CI job that installs dolfinx with `pip install -e` (editable). All non-editable installs across the rest of CI (the PETSc-enabled matrix in ccpp.yml, plus repeated local reproduction with an editable-install of this exact branch) succeed reliably. The new stub generation puts `dolfinx/cpp/*.pyi` (a directory of type stubs, matching nanobind's own convention for a compiled extension with nested submodules) directly alongside the compiled `dolfinx/cpp.<ext>` module. scikit-build-core's editable-install redirect builds a manifest that classifies each installed path as either a "wheel file" (compiled/source module) or a namespace-package search location; a `.pyi`-only directory that exactly shadows a compiled module's own name is an edge case scikit-build-core's own source comments show has caused prior classification bugs in this exact area (upstream issues #1427, #1482). This is a good fit for what we observe: the module resolves fine via the ordinary installed-path loader, but not through the editable redirect on this platform. This CI job doesn't need editable mode -- it builds once and immediately runs demos/tests against that one build, with no edit-and-rerun step in between -- so switching to a regular install sidesteps the redirect entirely rather than chasing the exact upstream classification bug. * Fix editable installs by requiring scikit-build-core>=1.0.0 Root-cause fix, replacing the earlier non-editable CI workaround (previous commit): editable installs were never actually broken by this PR's own code, but by a real bug in scikit-build-core <1.0.0's editable redirect finder. Empirically bisected locally (macOS, reproduced 100% on 0.11.0 through 0.12.2, 0/10 failures from 1.0.0 onward): the pre-1.0 redirect finder resolves a compiled module straight from its known file path via importlib.util.spec_from_file_location, without checking what else is on disk. nanobind's generated dolfinx/cpp/*.pyi stub directory (its standard convention for a compiled extension with nested submodules) sits right next to the compiled dolfinx/cpp.<ext> module, and the pre-1.0 build-time manifest scan registers dolfinx.cpp both as a "wheel file" (the .so) and, from the stub directory's __init__.pyi, as a package with its own search location -- confusing every subsequent `from dolfinx import cpp` in dolfinx/common.py. 1.0.0 resolves compiled modules through PathFinder instead, which correctly prefers the real file over the same-named stub directory regardless of the manifest ambiguity. Since this is a real upstream fix rather than a workaround, restore the RHEL/Spack CI job's editable install. That job's pinned Spack package repo only provides py-scikit-build-core up to 0.12.2 (confirmed by checking out the exact packages_ref tag), so pip-upgrade scikit-build-core to >=1.0.0 from PyPI specifically for that build step rather than relying on the Spack-provided one. * Fix two mypy ignores caused by name reuse across incompatible types demo_axis.py reused the module-level `sys` (the stdlib module, used for sys.argv) as a local PETSc.Sys() instance; mypy forbids narrowing a name to an incompatible type within the same scope. Renamed to petsc_sys, which needs no suppression at all. fem/assemble.py's _assemble_matrix_csr had the same pattern on the `bcs` parameter, reassigning it from Sequence[DirichletBC] | None to a list of raw _cpp_object handles. Renamed to _bcs (matching the existing convention in fem/petsc.py), which resolves the [misc] redefinition error. The [arg-type] ignore on the following _cpp.fem.assemble_matrix call stays -- confirmed via mypy that it suppresses three separate, genuine dtype-Union-vs-concrete-overload mismatches unrelated to the renaming. Verified with ruff check/format and a targeted mypy run against the built stubs: demo_axis.py now has zero errors, assemble.py's remaining ignore is the minimal one needed. * Fix genuinely-fixable mypy ignores in PETSc/scipy demos - demo_pyamg.py: narrow poisson_problem's dtype parameter from npt.DTypeLike to type[np.floating] | type[np.complexfloating], matching how it's actually called. This also exposed that dirichletbc's own value type hint was too narrow -- it already handles anything with a .dtype attribute at runtime, just didn't declare it -- so widen fem/bcs.py's dirichletbc signature to include raw numpy scalars instead of reaching for a lossy .item() conversion (which would have silently upcast float32 boundary values to float64). - demo_pml.py / demo_scattering-boundary-conditions.py: scipy-stubs does support complex arguments to jv, just typed as numpy.complex128 /complex64, not builtin complex -- wrap m * alpha accordingly. This uncovered a real bug: compute_a was annotated -> float but always returns a genuinely complex Mie coefficient (callers already take np.real/np.abs of it) -- fixed to -> complex in both files. - demo_mixed-topology.py: cast hexahedron/prism's _cpp_object to CoordinateElement_float64 (both are built with the default dtype=np.float64, so this matches runtime reality) instead of ignoring the dtype-Union mismatch. This gives create_mesh's return type real precision, which surfaced two more pre-existing errors further down the same file that were previously masked by the broken overload match; added targeted ignores for those (same wrapper-Union-vs-concrete-overload pattern as elsewhere, no clean local fix available). - assemble.py: insert_diagonal was still passed the stale `bcs` name after the earlier _bcs rename, a runtime bug (TypeError) hidden by a bare `# type: ignore`; fixed to reference _bcs, with the ignore narrowed to [call-overload] to match the actual error code. Verified with ruff check/format, mypy against the built stubs, and by actually running demo_pyamg.py (all four dtypes, correct precision preserved) and demo_mixed-topology.py (runs through everything touched here; its pre-existing failure further on, unrelated to this change, reproduces identically on unmodified main). * Clarify DirichletBC::set docs on ghost/owned-only x and x0 length Neither the Python nor the C++ doc comment previously explained why passing x with or without ghost entries changes what set() does. Traced the mechanism in DirichletBC.h's apply() lambda: _dofs0 always contains both owned and ghost dof indices, and the per-entry bounds check `_dofs0[i] < x.size()` is what makes an owned-only x safe (ghost indices are simply skipped) as well as a full local+ghost x (both get set). Also document that x0, when provided, must be at least as long as x -- only checked via assert in Debug/Developer builds, not a per-element bounds check like x itself. * Fix Group C: use PETSc.Vec.array_w instead of widening DirichletBC.set demo_static-condensation.py was the only demo passing a raw PETSc.Vec directly to DirichletBC.set(), which only works at runtime because nanobind's ndarray caster happens to accept anything satisfying the buffer protocol -- but statically needs an npt.NDArray, and PETSc.Vec isn't typed as satisfying that anywhere. Every other demo doing the identical assemble_vector/apply_lifting/ghostUpdate/set sequence (demo_elasticity.py, demo_stokes.py) already uses b.array_w for exactly this call; demo_static-condensation.py had just missed it. Widening fem/bcs.py's DirichletBC.set signature to accept a buffer-like type was considered and rejected: the underlying nanobind binding's own generated stub types x as a concrete ndarray[float64, ...] regardless, so a Python-level widening would only relocate the mismatch rather than resolve it, and would require either a hard petsc4py dependency (which fem/bcs.py deliberately avoids) or a buffer-protocol Protocol requiring Python's 3.12+ collections.abc.Buffer (project floor is 3.11). Verified with ruff check/format and mypy against the built stubs; b remains the same PETSc.Vec object afterward (array_w is a zero-copy view), so the later solver.solve(b, ...) call is unaffected. * Fix two real bugs found while reviewing bcs.py's type: ignore comments locate_dofs_geometrical/locate_dofs_topological's docstrings claimed that passing an iterable of function spaces returns "a 2-D array of shape (number of dofs, 2)". This is wrong: both the C++ implementation (std::array<std::vector<int32_t>, 2>) and every actual call site (test_bcs.py's dofs[0]/dofs[1] indexing) treat it as a list of one array per space. Fixed the docstrings, and split each function into @overload declarations so the return type (np.ndarray vs. list[np.ndarray]) is correctly narrowed per call site -- a plain Union return type was tried first and broke dofs= type-checking in 15 demos that pass a single FunctionSpace, since mypy can't tell from the Union alone which branch a given call site takes. That overload split then surfaced a second real bug: dirichletbc's `dofs` parameter was typed as a single ndarray only, but its C++ constructor also has a Sequence[ndarray]-accepting overload used when V is a sub-space and value's function space differs (e.g. demo_matrix-free-petsc.py, passing the dof-index pair straight from locate_dofs_topological((W.sub(0), V), ...)). Widened dofs to npt.NDArray[np.int32] | Sequence[npt.NDArray[np.int32]] and corrected the docstring accordingly. The remaining 5 ignores in this file (DirichletBC.set, and the dtype-dispatch construction in dirichletbc()) are the same wrapper-stores-a-dtype-Union-then-dispatches-at-runtime pattern seen throughout this codebase: bctype/`_value`/`self._cpp_object` are only known to be a *consistent* concrete dtype at runtime (via the cpp_types[dtype, geometry_dtype] lookup table), which mypy cannot verify statically. A typing.cast here would have to pick one of four concrete types with no static basis for which -- unlike the fixes above, there is no sound local fix without restructuring the dispatch mechanism itself, so these are left as targeted ignores. Verified with ruff check/format, mypy (-p dolfinx, test, and demo, all clean, matching CI's exact invocation), and pytest (test/unit/fem/test_bcs.py, 24/24 passing). * Fix two real bugs breaking CI: demo_mixed-topology.py and petsc.py contains() demo_mixed-topology.py crashed on every CI run (confirmed identical on unmodified main via git stash, so unrelated to this PR's own commits): `dirichletbc(value=0.0, dofs=bcdofs, V=V_cpp)` passed a raw C++ FunctionSpace built from a raw C++ Mesh (both from the low-level dolfinx.cpp.mesh.create_mesh binding this demo uses directly, since UFL doesn't yet support mixed-topology domains). dirichletbc needs V.mesh to have a real UFL domain to build the Constant for the boundary value, but a raw cpp Mesh has no ufl_domain()/_ufl_is_terminal_. Fixed by reusing the same Mesh(mesh, domain)/FunctionSpace(...) wrapping idiom the file already uses later (line ~186) for form assembly -- picking one cell type's domain/element arbitrarily, since neither is used for anything beyond this association. fem/petsc.py's _assemble_matrix_petsc called `row_forms[0].function_spaces[0].contains(bc.function_space)`, but `.contains()`'s only overload takes a raw cpp FunctionSpace while `bc.function_space` returns the Python wrapper -- a TypeError on every block-assembled LinearProblem.solve() with a DirichletBC, breaking demo_stokes.py's nested_iterative_solver_high_level and demo_mixed-poisson.py in the PETSc-enabled CI matrix. Fixed by passing bc.function_space._cpp_object instead. (This fix already existed uncommitted in the worktree from earlier work -- committing it now since it's exactly what these two failing demos need.) Verified demo_mixed-topology.py runs to completion locally (prints "Solution vector norm ...", no exceptions) plus a clean mypy/ruff pass. petsc.py's fix verified against the exact CI traceback (same file, same line, same call site in both demo_stokes.py and demo_mixed-poisson.py); could not run it directly in this session's non-PETSc local build, but the fix is unambiguous: .contains()'s sole registered overload requires a raw cpp FunctionSpace_float64, which ._cpp_object provides and the bare wrapper does not. * Use typing.overload to give shape-dependent return types real precision fem/forms.py: - form(): had no return type annotation at all, so mypy inferred Any for every call regardless of input shape -- zero type checking on one of the most heavily-used functions in the library. Added @overload for the four documented shapes (single ufl.Form, Sequence, Sequence of Sequence, None), matching the existing pattern already used by pack_constants/pack_coefficients/extract_function_spaces in the same file. - derivative_block(): same shape-dependent-return problem, already spelled out explicitly in its own docstring's four cases (cases 1 and 3 share the same static F: ufl.Form, u: Function signature, so they collapse into one overload). Fixing this uncovered a real, independent bug: NonlinearProblem.__init__ did `if J is None: J = derivative_block(F, u)`, and derivative_block's old, imprecise Union return type let that reassignment silently mask that the previous `J: ... | None` declared type never actually got narrowed past the None-check for mypy's purposes. Now fixed for real by the overload split. fem/petsc.py: - LinearProblem and NonlinearProblem made Generic[_U] (_U bound to Function | Sequence[Function]), with @overload on __init__ using the `self: LinearProblem[_Function]` / `self: LinearProblem[Sequence[_Function]]` self-type trick to bind _U from the shape of `a`/`L`/`F`/`u` at construction time. .solve() and the .u property now return _U directly instead of the previous flat Union, so e.g. `uh = LinearProblem(a, L, ...).solve(); uh.x.array[...] = ...` type-checks correctly instead of failing with `Item "Sequence[Function]" has no attribute "x"` regardless of whether a/L were given as single forms or block/nest sequences. Did not attempt to similarly parameterize the `.a`/`.L`/`.preconditioner` properties: `@property` cannot itself be `@overload`-ed (verified directly), and their shapes don't map from _U by simple identity like `.u`/`.solve()` do, so doing this properly would need additional correlated TypeVars for uncertain extra benefit -- left as the pre-existing Union. - Fixing LinearProblem.solve() surfaced a real narrowing gap along the way: it assembled the preconditioner guarded by `if self.P_mat is not None`, but used `self.preconditioner` inside the block -- two separate (if always correlated) attributes, so mypy couldn't narrow the one actually being used. Swapped the guard to check `self.preconditioner` (what's actually used) plus an `assert self.P_mat is not None` (still true by the constructor's own invariant, now made explicit rather than relied upon implicitly). Also added the missing `| None` to LinearProblem.preconditioner's return type and a `_preconditioner` class-level annotation on NonlinearProblem, both required for these to type-check once derivative_block's fix stopped papering over them. Verified with ruff check/format and mypy (-p dolfinx, demo, and test, matching CI's exact invocation) -- both with and without petsc4py installed in the venv, since a prior investigation this session found that mypy silently no-ops on PETSc-touching code when petsc4py is absent (ignore_missing_imports collapses it to Any). With petsc4py installed, mypy demo reproduces the exact same 46 pre-existing, unrelated PETSc-stub errors before and after this change -- confirming zero regressions. Directly verified the fix with standalone reveal_type scripts for both the single-form and block/nest constructor shapes of LinearProblem and NonlinearProblem. * fem/forms.py: reorder overloads so bare ufl.Form comes last mypy resolves ufl.Form as Any (ufl's @ufl_type() class decorator is unannotated), so an overload with a bare ufl.Form parameter placed before Sequence[ufl.Form]/Sequence[Sequence[ufl.Form]]/None variants makes those later overloads unreachable, since Any is treated as "the same or broader" than any other type. Putting the bare-Form overload last for form() and derivative_block() fixes the overload-cannot-match mypy errors without changing behaviour. * fem/forms.py: clarify docstring for None handling in form() Explain that None can appear anywhere in a nested block-form sequence to mark a zero block, and is passed through position-wise in the result rather than being compiled. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Tidy up * Update docs * Update --------- Co-authored-by: qbisicwate <qbisicwate@gmail.com> Co-authored-by: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Co-authored-by: Jack S. Hale <mail@jackhale.co.uk> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Hi, this patch generate stub files for dolfinx.cpp module using nanobind's builtin nanobind_add_stub macro.
An extra marker file py.typed will be generated under site-packages/dolfinx/cpp/py.typed. Not sure if this is needed as we have a top-level one in site-packages/dolfinx/py.typed.